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