Ticket #244: josm-search.patch
File josm-search.patch, 8.4 KB (added by , 18 years ago) |
---|
-
src/org/openstreetmap/josm/actions/search/SearchCompiler.java
5 5 import java.io.PushbackReader; 6 6 import java.io.StringReader; 7 7 import java.util.Map.Entry; 8 import java.util.Stack; 8 9 9 10 import org.openstreetmap.josm.data.osm.Node; 10 11 import org.openstreetmap.josm.data.osm.OsmPrimitive; 11 12 import org.openstreetmap.josm.data.osm.Segment; 12 13 import org.openstreetmap.josm.data.osm.Way; 14 import org.openstreetmap.josm.data.osm.User; 13 15 14 16 /** 15 17 * Implements a google-like search. … … 18 20 public class SearchCompiler { 19 21 20 22 boolean caseSensitive = false; 23 private static enum Operator { OpParen, OpOr, OpAnd, OpNot }; 21 24 22 25 abstract public static class Match { 23 26 abstract public boolean match(OsmPrimitive osm); … … 67 70 @Override public String toString() {return "id="+id;} 68 71 } 69 72 73 private static class UserMatch extends Match { 74 private User user; 75 public UserMatch(String username) {this.user = username.equals("")?null:User.get(username);} 76 @Override public boolean match(OsmPrimitive osm) { 77 return osm.user == user; 78 } 79 @Override public String toString() {return "user="+(user==null?"(null)":user.name);} 80 } 81 70 82 private class KeyValue extends Match { 71 83 private String key; 72 84 private String value; … … 150 162 151 163 /** 152 164 * The token returned is <code>null</code> or starts with an identifier character: 153 * - for an '-'. This will be the only character165 * - for an '-'. Similarly '(' and ')' are returned as is 154 166 * : for an key. The value is the next token 155 167 * | for "OR" 156 168 * ' ' for anything else. … … 170 182 switch (c) { 171 183 case '-': 172 184 return "-"; 185 case '(': 186 return "("; 187 case ')': 188 return ")"; 173 189 case '"': 174 190 s = new StringBuilder(" "); 175 191 for (int nc = search.read(); nc != -1 && nc != '"'; nc = search.read()) … … 191 207 return " "+s.toString(); 192 208 } 193 209 c = (char)next; 194 if (c == ' ' || c == '\t' || c == ':' || c == '"' ) {210 if (c == ' ' || c == '\t' || c == ':' || c == '"' || c == '(' || c == ')' ) { 195 211 if (c == ':') 196 212 return ":"+s.toString(); 197 213 search.unread(next); … … 207 223 } 208 224 209 225 210 private boolean notKey = false;211 226 private boolean notValue = false; 212 private boolean or = false;213 227 private String key = null; 214 228 String token = null; 229 /* Takes a key and a possible value and builds it into a match object */ 215 230 private Match build() { 216 231 String value = token.substring(1); 217 232 if (key == null) { … … 227 242 return notValue ? new Not(c) : c; 228 243 } 229 244 Match c; 230 if (key.equals("type")) 245 if (key.equals("type")) { 231 246 c = new ExactType(value); 232 else if (key.equals("property")) {247 } else if (key.equals("property")) { 233 248 String realKey = "", realValue = value; 234 249 int eqPos = value.indexOf("="); 235 250 if (eqPos != -1) { … … 237 252 realValue = value.substring(eqPos+1); 238 253 } 239 254 c = new KeyValue(realKey, realValue, notValue); 255 notValue = false; 240 256 } else if (key.equals("id")) { 241 257 try { 242 258 c = new Id(Long.parseLong(value)); 243 259 } catch (NumberFormatException x) { 244 260 c = new Id(0); 245 261 } 246 if (notValue)247 c = new Not(c);248 } else 262 } else if (key.equals("user")) { 263 c = new UserMatch(value); 264 } else { 249 265 c = new KeyValue(key, value, notValue); 250 if (notKey) 251 return new Not(c); 266 notValue = false; 267 } 268 if (notValue) 269 c = new Not(c); 252 270 return c; 253 271 } 254 272 273 /* Stacks for managing the operator precedence */ 274 private Stack<Operator> opstack; 275 private Stack<Match> argstack; 276 /* Push the operator on the stack, but only after ensuring all giher precedence operators are applied */ 277 private void pushOp(Operator op) 278 { 279 while( !opstack.empty() && opstack.peek().compareTo(op) > 0) 280 applyOp(); 281 opstack.push(op); 282 } 283 /* A close parenthesis applies all operators until the open paren */ 284 private void closeParen() 285 { 286 while( !opstack.empty() && opstack.peek() != Operator.OpParen) 287 applyOp(); 288 if( opstack.empty() ) 289 throw new Error( "Too many close parenthesis" ); 290 opstack.pop(); 291 } 292 private void applyOp() 293 { 294 try { 295 switch( opstack.pop() ) 296 { 297 case OpAnd: 298 argstack.push( new And(argstack.pop(), argstack.pop()) ); 299 break; 300 case OpOr: 301 argstack.push( new Or(argstack.pop(), argstack.pop()) ); 302 break; 303 case OpNot: 304 argstack.push( new Not(argstack.pop()) ); 305 break; 306 default: 307 throw new Error( "Unknown operator" ); 308 } 309 } 310 catch( java.util.EmptyStackException e ) 311 { 312 throw new Error("Missing argument"); 313 } 314 } 315 /* The actual parser */ 255 316 private Match parse(PushbackReader search) { 256 Match result = null; 317 argstack = new Stack<Match>(); 318 opstack = new Stack<Operator>(); 319 320 pushOp( Operator.OpParen ); 321 boolean hadArg = false; 257 322 for (token = nextToken(search); token != null; token = nextToken(search)) { 323 // System.out.println( "token:["+token+"], opstack:"+opstack+", argstack:"+argstack+", hadArg:"+hadArg ); 258 324 if (token.equals("-")) 259 notValue = true; 260 else if (token.equals("|")) { 261 if (result == null) 262 continue; 263 or = true; 264 notValue = false; 265 } else if (token.startsWith(":")) { 325 { 326 if( hadArg ) 327 pushOp(Operator.OpAnd); 328 hadArg = false; 329 if( key == null ) 330 pushOp(Operator.OpNot); 331 else 332 notValue = true; 333 } 334 else if (token.equals("(")) 335 { 336 if( hadArg ) 337 pushOp(Operator.OpAnd); 338 hadArg = false; 339 opstack.push(Operator.OpParen); 340 } 341 else if (token.equals(")")) 342 { 343 hadArg = true; 344 closeParen(); 345 } 346 else if (token.equals("|")) 347 { 348 pushOp(Operator.OpOr); 349 hadArg = false; 350 } 351 else if (token.startsWith(":")) { 352 if( hadArg ) 353 pushOp(Operator.OpAnd); 354 hadArg = false; 266 355 if (key == null) { 267 356 key = token.substring(1); 268 notKey = notValue;269 357 notValue = false; 270 358 } else 271 359 key += token.substring(1); 272 360 } else { 361 if( hadArg ) 362 pushOp(Operator.OpAnd); 273 363 Match current = build(); 274 if (result == null) 275 result = current; 276 else 277 result = or ? new Or(result, current) : new And(result, current); 278 key = null; 279 notKey = false; 280 notValue = false; 281 or = false; 282 } 364 argstack.push(current); 365 hadArg = true; 366 key = null; 367 notValue = false; 368 } 283 369 } 370 // System.out.println( "End loop: opstack:"+opstack+", argstack:"+argstack+", hadArg:"+hadArg ); 284 371 // if "key:" was the last search 285 372 if (key != null) { 373 if( hadArg ) 374 pushOp(Operator.OpAnd); 286 375 token = " "; 287 376 Match current = build(); 288 result = (result == null) ? current : new And(result,current);377 argstack.push(current); 289 378 } 290 return result == null ? new Always() : result; 379 closeParen(); 380 // System.out.println( "Finish: opstack:"+opstack+", argstack:"+argstack+", hadArg:"+hadArg ); 381 if( !opstack.empty() || argstack.size() > 1 ) 382 throw new Error("Parse error"); 383 if( argstack.empty() ) 384 return new Always(); 385 return argstack.pop(); 291 386 } 292 387 } -
src/org/openstreetmap/josm/actions/search/SearchAction.java
85 85 } 86 86 } 87 87 Collection<OsmPrimitive> sel = Main.ds.getSelected(); 88 SearchCompiler.Match matcher = SearchCompiler.compile(search, caseSensitive); 89 for (OsmPrimitive osm : Main.ds.allNonDeletedPrimitives()) { 88 try { 89 SearchCompiler.Match matcher = SearchCompiler.compile(search, caseSensitive); 90 for (OsmPrimitive osm : Main.ds.allNonDeletedPrimitives()) { 90 91 if (mode == SearchMode.replace) { 91 92 if (matcher.match(osm)) 92 93 sel.add(osm); … … 97 98 else if (mode == SearchMode.remove && osm.selected && matcher.match(osm)) 98 99 sel.remove(osm); 99 100 } 101 } catch( Error e ) { 102 JOptionPane.showMessageDialog(Main.parent, tr("Error in expression")); 103 return; 104 } 100 105 Main.ds.setSelected(sel); 101 106 } 102 107 }