The attached patch adds regex support to search, this solves trac
ticket #1864 [1][2], it adds a new "regex search" checkbox to the
search dialog below "case sensitive". If it's checked the pattern
given will be compiled as a regex with java.util.regex, e.g.:

^hig.*?[way]+$ # Matches in both keys and values
^foo$:[bar]       # Seperate regexes for the key and value

Patterns are case-insensitive by default, if the "case sensitive"
checkbox is checked they will be compiled with
Pattern.CASE_INSENSITIVE set (instead of lower casing the regex or
pattern which would screw things up).

If the user enters an invalid regex a dialog like this (by throwing
ParseError) shows up:

http://flickr.com/photos/avarab/3178963199/sizes/o/

It is of course translatable but the "full error" part comes directly
from Java's e.getMessage() exception method and may thus not be in the
same language as the rest of the JOSM interface. Although Java
implementations might have it localized. I think this is acceptable
given the limitations, the alternative is to provide a "regex
compilation failed" error without any further detail.

1. http://josm.openstreetmap.de/ticket/1864
2. I couldn't submit it to trac, it gave me: "500 Internal Server
Error (Submission rejected as potential spam (Content contained
blacklisted patterns))"
Index: src/org/openstreetmap/josm/Main.java
===================================================================
--- src/org/openstreetmap/josm/Main.java	(revision 1212)
+++ src/org/openstreetmap/josm/Main.java	(working copy)
@@ -449,7 +449,7 @@
                 downloadFromParamString(true, s);
         if (args.containsKey("selection"))
             for (String s : args.get("selection"))
-                SearchAction.search(s, SearchAction.SearchMode.add, false);
+                SearchAction.search(s, SearchAction.SearchMode.add, false, false);
     }
 
     public static boolean breakBecauseUnsavedChanges() {
Index: src/org/openstreetmap/josm/actions/search/SearchCompiler.java
===================================================================
--- src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 1212)
+++ src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(working copy)
@@ -6,6 +6,7 @@
 import java.io.PushbackReader;
 import java.io.StringReader;
 import java.util.Map.Entry;
+import java.util.regex.*;
 
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -20,15 +21,18 @@
 public class SearchCompiler {
 
     private boolean caseSensitive = false;
+    private boolean regexSearch = false;
+    private String  rxErrorMsg = "The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}";
     private PushbackTokenizer tokenizer;
 
-    public SearchCompiler(boolean caseSensitive, PushbackTokenizer tokenizer) {
+    public SearchCompiler(boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) {
         this.caseSensitive = caseSensitive;
+        this.regexSearch = regexSearch;
         this.tokenizer = tokenizer;
     }
 
     abstract public static class Match {
-        abstract public boolean match(OsmPrimitive osm);
+        abstract public boolean match(OsmPrimitive osm) throws ParseError;
     }
 
     private static class Always extends Match {
@@ -40,7 +44,7 @@
     private static class Not extends Match {
         private final Match match;
         public Not(Match match) {this.match = match;}
-        @Override public boolean match(OsmPrimitive osm) {
+        @Override public boolean match(OsmPrimitive osm) throws ParseError {
             return !match.match(osm);
         }
         @Override public String toString() {return "!"+match;}
@@ -50,7 +54,7 @@
         private Match lhs;
         private Match rhs;
         public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
-        @Override public boolean match(OsmPrimitive osm) {
+        @Override public boolean match(OsmPrimitive osm) throws ParseError {
             return lhs.match(osm) && rhs.match(osm);
         }
         @Override public String toString() {return lhs+" && "+rhs;}
@@ -60,7 +64,7 @@
         private Match lhs;
         private Match rhs;
         public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
-        @Override public boolean match(OsmPrimitive osm) {
+        @Override public boolean match(OsmPrimitive osm) throws ParseError {
             return lhs.match(osm) || rhs.match(osm);
         }
         @Override public String toString() {return lhs+" || "+rhs;}
@@ -79,20 +83,82 @@
         private String key;
         private String value;
         public KeyValue(String key, String value) {this.key = key; this.value = value; }
-        @Override public boolean match(OsmPrimitive osm) {
-            String value = null;
-            if (key.equals("timestamp"))
-                value = osm.getTimeStr();
-            else
-                value = osm.get(key);
-            if (value == null)
-                return false;
-            String v1 = caseSensitive ? value : value.toLowerCase();
-            String v2 = caseSensitive ? this.value : this.value.toLowerCase();
-            // is not Java 1.5
-            //v1 = java.text.Normalizer.normalize(v1, java.text.Normalizer.Form.NFC);
-            //v2 = java.text.Normalizer.normalize(v2, java.text.Normalizer.Form.NFC);
-            return v1.indexOf(v2) != -1;
+        @Override public boolean match(OsmPrimitive osm) throws ParseError {
+
+            if (regexSearch) { 
+                if (osm.keys == null)
+                    return false;
+
+                /* The string search will just get a key like
+                 * 'highway' and look that up as osm.get(key). But
+                 * since we're doing a regex match we'll have to loop
+                 * over all the keys to see if they match our regex,
+                 * and only then try to match against the value
+                 */
+
+                Pattern searchKey   = null;
+                Pattern searchValue = null;
+
+                if (caseSensitive) {
+                    try {
+                        searchKey = Pattern.compile(key);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                    try {
+                        searchValue = Pattern.compile(value);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                } else {
+                    try {
+                        searchKey = Pattern.compile(key, Pattern.CASE_INSENSITIVE);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                    try {
+                        searchValue = Pattern.compile(value, Pattern.CASE_INSENSITIVE);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                }
+
+                for (Entry<String, String> e : osm.keys.entrySet()) {
+                    String k = e.getKey();
+                    String v = e.getValue();
+
+                    Matcher matcherKey = searchKey.matcher(k);
+                    boolean matchedKey = matcherKey.find();
+
+                    if (matchedKey) {
+                        Matcher matcherValue = searchValue.matcher(v);
+                        boolean matchedValue = matcherValue.find();
+
+                        if (matchedValue)
+                            return true;
+                    }
+                }
+            } else {
+                String value = null;
+
+                if (key.equals("timestamp"))
+                    value = osm.getTimeStr();
+                else
+                    value = osm.get(key);
+
+                if (value == null)
+                    return false;
+
+                String v1 = caseSensitive ? value : value.toLowerCase();
+                String v2 = caseSensitive ? this.value : this.value.toLowerCase();
+
+                // is not Java 1.5
+                //v1 = java.text.Normalizer.normalize(v1, java.text.Normalizer.Form.NFC);
+                //v2 = java.text.Normalizer.normalize(v2, java.text.Normalizer.Form.NFC);
+                return v1.indexOf(v2) != -1;
+            }
+
+            return false;
         }
         @Override public String toString() {return key+"="+value;}
     }
@@ -100,19 +166,58 @@
     private class Any extends Match {
         private String s;
         public Any(String s) {this.s = s;}
-        @Override public boolean match(OsmPrimitive osm) {
+        @Override public boolean match(OsmPrimitive osm) throws ParseError {
             if (osm.keys == null)
                 return s.equals("");
-            String search = caseSensitive ? s : s.toLowerCase();
+
+            String search;
+            Pattern searchRegex = null;
+
+            if (regexSearch) {
+                search = s;
+                if (caseSensitive) {
+                    try {
+                        searchRegex = Pattern.compile(search);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                } else {
+                    try {
+                        searchRegex = Pattern.compile(search, Pattern.CASE_INSENSITIVE);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                }
+            } else {
+                search = caseSensitive ? s : s.toLowerCase();
+            }
+
             // is not Java 1.5
             //search = java.text.Normalizer.normalize(search, java.text.Normalizer.Form.NFC);
             for (Entry<String, String> e : osm.keys.entrySet()) {
-                String key = caseSensitive ? e.getKey() : e.getKey().toLowerCase();
-                String value = caseSensitive ? e.getValue() : e.getValue().toLowerCase();
+
                 // is not Java 1.5
                 //value = java.text.Normalizer.normalize(value, java.text.Normalizer.Form.NFC);
-                if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
-                    return true;
+
+                if (regexSearch) {
+                    String key = e.getKey();
+                    String value = e.getValue();
+                    
+                    Matcher keyMatcher = searchRegex.matcher(key);
+                    Matcher valMatcher = searchRegex.matcher(value);
+
+                    boolean keyMatchFound = keyMatcher.find();
+                    boolean valMatchFound = valMatcher.find();
+
+                    if (keyMatchFound || valMatchFound)
+                        return true;
+                } else {
+                    String key = caseSensitive ? e.getKey() : e.getKey().toLowerCase();
+                    String value = caseSensitive ? e.getValue() : e.getValue().toLowerCase();
+
+                    if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
+                        return true;
+                } 
             }
             if (osm.user != null) {
                 String name = osm.user.name;
@@ -188,9 +293,9 @@
         }
     }
 
-    public static Match compile(String searchStr, boolean caseSensitive)
+    public static Match compile(String searchStr, boolean caseSensitive, boolean regexSearch)
             throws ParseError {
-        return new SearchCompiler(caseSensitive,
+        return new SearchCompiler(caseSensitive, regexSearch,
                 new PushbackTokenizer(
                     new PushbackReader(new StringReader(searchStr))))
             .parse();
Index: src/org/openstreetmap/josm/actions/search/SearchAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 1212)
+++ src/org/openstreetmap/josm/actions/search/SearchAction.java	(working copy)
@@ -47,7 +47,7 @@
         }
         SearchSetting s = lastSearch;
         if (s == null)
-            s = new SearchSetting("", false, SearchMode.replace);
+            s = new SearchSetting("", false, false, SearchMode.replace);
         showSearchDialog(s);
     }
 
@@ -80,6 +80,7 @@
         bg.add(remove);
 
         JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
+        JCheckBox regexSearch   = new JCheckBox(tr("regex search"), initialValues.regexSearch);
 
         JPanel p = new JPanel(new GridBagLayout());
         p.add(label, GBC.eop());
@@ -88,6 +89,7 @@
         p.add(add, GBC.eol());
         p.add(remove, GBC.eop());
         p.add(caseSensitive, GBC.eol());
+        p.add(regexSearch, GBC.eol());
         JOptionPane pane = new JOptionPane(p, JOptionPane.INFORMATION_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null) {
             @Override
             public void selectInitialValue() {
@@ -101,7 +103,7 @@
         // User pressed OK - let's perform the search
         SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace
                 : (add.isSelected() ? SearchAction.SearchMode.add : SearchAction.SearchMode.remove);
-        SearchSetting setting = new SearchSetting(input.getText(), caseSensitive.isSelected(), mode);
+        SearchSetting setting = new SearchSetting(input.getText(), caseSensitive.isSelected(), regexSearch.isSelected(), mode);
         searchWithHistory(setting);
     }
 
@@ -116,15 +118,15 @@
         while (searchHistory.size() > SEARCH_HISTORY_SIZE)
             searchHistory.removeLast();
         lastSearch = s;
-        search(s.text, s.mode, s.caseSensitive);
+        search(s.text, s.mode, s.caseSensitive, s.regexSearch);
     }
 
     public static void searchWithoutHistory(SearchSetting s) {
         lastSearch = s;
-        search(s.text, s.mode, s.caseSensitive);
+        search(s.text, s.mode, s.caseSensitive, s.regexSearch);
     }
 
-    public static void search(String search, SearchMode mode, boolean caseSensitive) {
+    public static void search(String search, SearchMode mode, boolean caseSensitive, boolean regexSearch) {
         if (search.startsWith("http://";) || search.startsWith("ftp://";) || search.startsWith("https://";)
                 || search.startsWith("file:/")) {
             SelectionWebsiteLoader loader = new SelectionWebsiteLoader(search, mode);
@@ -135,7 +137,7 @@
         }
         try {
             Collection<OsmPrimitive> sel = Main.ds.getSelected();
-            SearchCompiler.Match matcher = SearchCompiler.compile(search, caseSensitive);
+            SearchCompiler.Match matcher = SearchCompiler.compile(search, caseSensitive, regexSearch);
             int foundMatches = 0;
             for (OsmPrimitive osm : Main.ds.allNonDeletedCompletePrimitives()) {
                 if (mode == SearchMode.replace) {
@@ -174,10 +176,12 @@
         String text;
         SearchMode mode;
         boolean caseSensitive;
+        boolean regexSearch;
 
-        public SearchSetting(String text, boolean caseSensitive, SearchMode mode) {
+        public SearchSetting(String text, boolean caseSensitive, boolean regexSearch, SearchMode mode) {
             super();
             this.caseSensitive = caseSensitive;
+            this.regexSearch = regexSearch;
             this.mode = mode;
             this.text = text;
         }
@@ -185,7 +189,8 @@
         @Override
         public String toString() {
             String cs = caseSensitive ? tr("CI") : tr("CS");
-            return "\"" + text + "\" (" + cs + ", " + mode + ")";
+            String rx = regexSearch ? (", " + tr("RX")) : "";
+            return "\"" + text + "\" (" + cs + rx + ", " + mode + ")";
         }
 
     }
_______________________________________________
josm-dev mailing list
[email protected]
http://lists.openstreetmap.org/listinfo/josm-dev

Reply via email to