On Thu, Jan 8, 2009 at 2:19 PM, Dirk Stöcker <[email protected]> wrote:
> On Thu, 8 Jan 2009, Ævar Arnfjörð Bjarmason wrote:
>
>> 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.:
>
> Thanks for your work.
>
>> ^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.
>
> Some notes: You cannot add any text without tr(). It will not get
> translated. Either you make a marktr() around or you add it directly at the
> place where it is used and add a tr().

I didn't know about that tr() restriction. Attached is a patch where I
duplicate the error message for all the places where there can be
regex errors, more duplication but it eliminates the class variable I
was using to store the message.

> About Java messages: When they aren't dynamic, we can translate them as
> well. Add a tr around the message text and have a
>
> tr("could not get audio input stream from input URL"); // Java message
> loading audio data
>
> in the i18n/specialmessages.java.
>
> If they are dynamic (e.g. contain variables), then we cannot translate them.
> Your example looks like they are dynamic.

Yes, that error message is dynamic.

>> 2. I couldn't submit it to trac, it gave me: "500 Internal Server
>> Error (Submission rejected as potential spam (Content contained
>> blacklisted patterns))"
>
> Making a zip sometimes helps :-)
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,17 @@
 public class SearchCompiler {
 
     private boolean caseSensitive = false;
+    private boolean regexSearch = false;
     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 +43,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 +53,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 +63,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 +82,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("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}", e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                    try {
+                        searchValue = Pattern.compile(value);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}", e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                } else {
+                    try {
+                        searchKey = Pattern.compile(key, Pattern.CASE_INSENSITIVE);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}", e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                    try {
+                        searchValue = Pattern.compile(value, Pattern.CASE_INSENSITIVE);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}", 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 +165,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("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}", e.getPattern(), e.getIndex(), e.getMessage()));
+                    }
+                } else {
+                    try {
+                        searchRegex = Pattern.compile(search, Pattern.CASE_INSENSITIVE);
+                    } catch (PatternSyntaxException e) {
+                        throw new ParseError(tr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}", 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 +292,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