// JOSM tag2link plugin.
// Copyright (C) 2011-2012 Don-vip & FrViPofm
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package org.openstreetmap.josm.plugins.tag2link;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.IPrimitive;
import org.openstreetmap.josm.data.osm.Tag;
import org.openstreetmap.josm.plugins.tag2link.data.Link;
import org.openstreetmap.josm.plugins.tag2link.data.LinkPost;
import org.openstreetmap.josm.plugins.tag2link.data.Rule;
import org.openstreetmap.josm.plugins.tag2link.data.Rule.EvalResult;
import org.openstreetmap.josm.plugins.tag2link.data.Rule.MatchingTag;
import org.openstreetmap.josm.plugins.tag2link.data.Source;
import org.openstreetmap.josm.plugins.tag2link.io.SourcesReader;
/**
* Class matching rules against a specified OSM primitive in order to get its relevant links.
* @author Don-vip
*
*/
public class Tag2LinkRuleChecker implements Tag2LinkConstants {
private static Collection<Source> sources = new ArrayList<>();
private static boolean initialized = false;
/**
* Initializes the matching rules mechanism.
*/
public static void init() {
if (!initialized) {
sources = SourcesReader.readSources();
initialized = true;
}
}
private static String findValue(String arg, Collection<MatchingTag> matchingTags) {
for (MatchingTag tag : matchingTags) {
if (tag.params.containsKey(arg)) {
return tag.params.get(arg);
}
}
return null;
}
private static String replaceParams(String s, EvalResult eval) {
String result = s;
Matcher m = Pattern.compile("%([^%]*)%").matcher(s);
while (m.find()) {
String arg = m.group(1);
// Search for a standard value
String val = findValue(arg, eval.matchingTags);
// No standard value found: test lang() function
if (val == null) {
Matcher lm = Pattern.compile(".*lang(?:\\((\\p{Lower}{2,})(?:,(\\p{Lower}{2,}))*\\))?.*").matcher(arg);
if (lm.matches()) {
String josmLang = Main.pref.get("language");
String jvmLang = (josmLang.isEmpty() ? Locale.getDefault().getLanguage() : josmLang).split("_")[0];
if (lm.groupCount() == 0) {
val = jvmLang;
} else {
for (int i = 1; i<=lm.groupCount() && val == null; i++) {
if (jvmLang.equals(lm.group(i))) {
val = jvmLang;
}
}
}
}
}
// Find a default value if set after ":"
if (val == null && arg.contains(":")) {
String[] vars = arg.split(":");
for (int i = 0; val == null && i < vars.length-1; i++) {
val = findValue(vars[i], eval.matchingTags);
}
if (val == null) {
// Default value
val = vars[vars.length-1];
}
}
// Has a value been found ?
if (val != null) {
try {
// Special hack for Wikipedia that prevents spaces being replaced by "+" characters, but by "_"
if (s.contains("wikipedia.") || s.contains("commons.wikimedia.org")) {
val = val.replaceAll(" ", "_");
}
// Encode param to be included in the URL, except if it is the URL itself !
if (!m.group().equals(s)) {
val = URLEncoder.encode(val, UTF8_ENCODING);
}
// Finally replace parameter
result = result.replaceFirst(Pattern.quote(m.group()), val);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
System.err.println("Invalid argument: "+arg);
}
}
return result;
}
private static void replaceMapParams(Map<String, String> map, EvalResult eval) {
for (Iterator<String> it = map.keySet().iterator(); it.hasNext(); ) {
String key = it.next();
String value = map.get(key);
String key2 = replaceParams(key, eval);
String value2 = replaceParams(value, eval);
if (key.equals(key2) && value.equals(value2)) {
// Nothing to do
} else if (key.equals(key2)) {
// Update value
map.put(key, value2);
} else {
// Update key, and maybe value
map.remove(key);
map.put(key2, value2);
}
}
}
private static Collection<Link> processEval(EvalResult eval, Rule rule, Source source) {
Collection<Link> result = new ArrayList<>();
if (eval.matches()) {
for (Link link : rule.links) {
try {
Link copy = (Link) link.clone();
copy.name = copy.name.replaceAll("%name%", source.name);
copy.url = replaceParams(copy.url, eval);
if (copy instanceof LinkPost) {
LinkPost lp = (LinkPost) copy;
replaceMapParams(lp.headers, eval);
replaceMapParams(lp.params, eval);
}
result.add(copy);
} catch (CloneNotSupportedException e) {
System.err.println(e);
}
}
}
return result;
}
/**
* Replies the links relevant to the given OSM primitive.
* @param p The OSM primitive
* @return the links relevant to the {@code p}.
*/
public static Collection<Link> getLinks(IPrimitive p) {
Collection<Link> result = new ArrayList<>();
for (Source source : sources) {
for (Rule rule : source.rules) {
result.addAll(processEval(rule.evaluates(p), rule, source));
}
}
return result;
}
/**
* Replies the links relevant to the given OSM tag.
* @param tag The OSM tag
* @return the links relevant to the {@code tag}.
*/
public static Collection<Link> getLinks(Tag tag) {
Collection<Link> result = new ArrayList<>();
for (Source source : sources) {
for (Rule rule : source.rules) {
result.addAll(processEval(rule.evaluates(tag), rule, source));
}
}
return result;
}
}