Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/Debouncer.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/Debouncer.java	(revision 32886)
+++ 	(revision )
@@ -1,36 +1,0 @@
-package org.wikipedia;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public class Debouncer {
-    private final ScheduledExecutorService scheduler;
-    private final ConcurrentHashMap<Object, Future<?>> delayedMap = new ConcurrentHashMap<>();
-
-    public Debouncer(ScheduledExecutorService scheduler) {
-        this.scheduler = scheduler;
-    }
-
-    /**
-     * Debounces {@code callable} by {@code delay}, i.e., schedules it to be executed after {@code delay},
-     * or cancels its execution if the method is called with the same key within the {@code delay} again.
-     */
-    public void debounce(final Object key, final Runnable runnable, long delay, TimeUnit unit) {
-        final Future<?> prev = delayedMap.put(key, scheduler.schedule(() -> {
-            try {
-                runnable.run();
-            } finally {
-                delayedMap.remove(key);
-            }
-        }, delay, unit));
-        if (prev != null) {
-            prev.cancel(true);
-        }
-    }
-
-    public void shutdown() {
-        scheduler.shutdownNow();
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/FetchWikidataAction.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/FetchWikidataAction.java	(revision 32886)
+++ 	(revision )
@@ -1,173 +1,0 @@
-// License: GPL. See LICENSE file for details./*
-package org.wikipedia;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
-
-import java.awt.event.ActionEvent;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.swing.JOptionPane;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.tools.MultiMap;
-import org.openstreetmap.josm.tools.Utils;
-
-public class FetchWikidataAction extends JosmAction {
-
-    public FetchWikidataAction() {
-        super(tr("Fetch Wikidata IDs"), "dialogs/wikidata",
-                tr("Fetch Wikidata IDs using the ''wikipedia'' tag"), null, true);
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        DataSet ds = getLayerManager().getEditDataSet();
-        if (ds == null) {
-            return;
-        }
-        Main.worker.submit(new Fetcher(ds.getSelected()));
-    }
-
-    static class Fetcher extends PleaseWaitRunnable {
-        private final Collection<? extends OsmPrimitive> selection;
-        private boolean canceled = false;
-        private final List<Command> commands = new ArrayList<>();
-        private final Collection<WikipediaApp.WikipediaLangArticle> notFound = new ArrayList<>();
-
-        public Fetcher(Collection<? extends OsmPrimitive> selection) {
-            super(tr("Fetching Wikidata IDs"));
-            this.selection = selection;
-        }
-
-        @Override
-        protected void cancel() {
-            canceled = true;
-        }
-
-        @Override
-        protected void realRun() {
-            final Map<String, PrimitivesWithWikipedia> wikipediaByLanguage = getLanguageToArticlesMap(selection);
-            getProgressMonitor().setTicksCount(wikipediaByLanguage.keySet().size());
-            for (final Map.Entry<String, PrimitivesWithWikipedia> i : wikipediaByLanguage.entrySet()) {
-                if (canceled) {
-                    break;
-                }
-                final PrimitivesWithWikipedia fetcher = i.getValue();
-                fetcher.updateWikidataIds(getProgressMonitor().createSubTaskMonitor(1, false));
-                final Command command = fetcher.getCommand();
-                if (command != null) {
-                    commands.add(command);
-                }
-                notFound.addAll(fetcher.getNotFound());
-            }
-        }
-
-        protected static Map<String, PrimitivesWithWikipedia> getLanguageToArticlesMap(final Iterable<? extends OsmPrimitive> selection) {
-            final Map<String, PrimitivesWithWikipedia> r = new HashMap<>();
-            for (final OsmPrimitive i : selection) {
-                final WikipediaApp.WikipediaLangArticle tag = WikipediaApp.WikipediaLangArticle.parseTag("wikipedia", i.get("wikipedia"));
-                if (tag != null) {
-                    if (!r.containsKey(tag.lang)) {
-                        r.put(tag.lang, new PrimitivesWithWikipedia(tag.lang));
-                    }
-                    r.get(tag.lang).put(i, tag.article);
-                }
-            }
-            return r;
-        }
-
-        @Override
-        protected void finish() {
-            if (!canceled && !commands.isEmpty()) {
-                Main.main.undoRedo.add(commands.size() == 1 ? commands.get(0) : new SequenceCommand(tr("Add Wikidata"), commands));
-            }
-            if (!canceled && !notFound.isEmpty()) {
-                new Notification(tr("No Wikidata ID found for: {0}", Utils.joinAsHtmlUnorderedList(notFound)))
-                        .setIcon(JOptionPane.WARNING_MESSAGE)
-                        .setDuration(Notification.TIME_LONG)
-                        .show();
-            }
-        }
-    }
-
-    static class PrimitivesWithWikipedia {
-        final String lang;
-        final MultiMap<String, OsmPrimitive> byArticle = new MultiMap<>();
-        final List<Command> commands = new ArrayList<>();
-        final List<WikipediaApp.WikipediaLangArticle> notFound = new ArrayList<>();
-
-        public PrimitivesWithWikipedia(String lang) {
-            this.lang = lang;
-        }
-
-        public void put(OsmPrimitive key, String wikipedia) {
-            byArticle.put(wikipedia, key);
-        }
-
-        protected void updateWikidataIds(ProgressMonitor monitor) {
-            final int size = byArticle.keySet().size();
-            monitor.beginTask(trn(
-                    "Fetching {0} Wikidata ID for language ''{1}''",
-                    "Fetching {0} Wikidata IDs for language ''{1}''", size, size, lang));
-            final Map<String, String> wikidataByWikipedia = WikipediaApp.getWikidataForArticles(lang, new ArrayList<>(byArticle.keySet()));
-            ConditionalOptionPaneUtil.startBulkOperation(GuiUtils.PREF_OVERWRITE);
-            for (Map.Entry<String, Set<OsmPrimitive>> i : byArticle.entrySet()) {
-                final String wikipedia = i.getKey();
-                final String wikidata = wikidataByWikipedia.get(wikipedia);
-                if (wikidata != null) {
-                    if (GuiUtils.confirmOverwrite("wikidata", wikidata, i.getValue())) {
-                        commands.add(new ChangePropertyCommand(i.getValue(), "wikidata", wikidata));
-                    }
-                } else {
-                    final WikipediaApp.WikipediaLangArticle article = new WikipediaApp.WikipediaLangArticle(lang, wikipedia);
-                    Main.warn(tr("No Wikidata ID found for: {0}", article));
-                    notFound.add(article);
-                }
-            }
-            ConditionalOptionPaneUtil.endBulkOperation(GuiUtils.PREF_OVERWRITE);
-            monitor.finishTask();
-        }
-
-        public Command getCommand() {
-            return commands.isEmpty()
-                    ? null
-                    : new SequenceCommand(tr("Add Wikidata for language ''{0}''", lang), commands);
-        }
-
-        public List<WikipediaApp.WikipediaLangArticle> getNotFound() {
-            return notFound;
-        }
-    }
-
-    @Override
-    protected void updateEnabledState() {
-        updateEnabledStateOnCurrentSelection();
-    }
-
-    @Override
-    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
-        for (final OsmPrimitive i : selection) {
-            if (i.hasKey("wikipedia")) {
-                setEnabled(true);
-                return;
-            }
-        }
-        setEnabled(false);
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/GuiUtils.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/GuiUtils.java	(revision 32886)
+++ 	(revision )
@@ -1,47 +1,0 @@
-// License: GPL. See LICENSE file for details./*
-package org.wikipedia;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
-import org.openstreetmap.josm.gui.DefaultNameFormatter;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.tools.AlphanumComparator;
-import org.openstreetmap.josm.tools.Utils;
-
-import javax.swing.JOptionPane;
-import java.util.Collection;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.stream.Collectors;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
-
-class GuiUtils {
-
-    static final String PREF_OVERWRITE = "wikipedia.overwrite-tag";
-
-    static boolean confirmOverwrite(final String key, final String newValue, final Collection<OsmPrimitive> primitives) {
-        final SortedSet<String> existingValues = primitives.stream()
-                .map(x -> x.get(key))
-                .filter(x -> x != null && !newValue.equals(x))
-                .collect(Collectors.toCollection(() -> new TreeSet<>(AlphanumComparator.getInstance())));
-
-        if (existingValues.isEmpty()) {
-            return true;
-        }
-        final Boolean r = GuiHelper.runInEDTAndWaitAndReturn(() ->
-                ConditionalOptionPaneUtil.showConfirmationDialog(PREF_OVERWRITE, Main.parent,
-                        trn(
-                                "Overwrite ''{0}'' tag {1} from {2} with new value ''{3}''?",
-                                "Overwrite ''{0}'' tags {1} from {2} with new value ''{3}''?", existingValues.size(),
-                                key, Utils.joinAsHtmlUnorderedList(existingValues),
-                                DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(primitives, 10), newValue),
-                        tr("Overwrite key"),
-                        JOptionPane.YES_NO_OPTION,
-                        JOptionPane.QUESTION_MESSAGE,
-                        JOptionPane.YES_OPTION));
-        return Boolean.TRUE.equals(r);
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikiSearchTextResultListPanel.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikiSearchTextResultListPanel.java	(revision 32886)
+++ 	(revision )
@@ -1,23 +1,0 @@
-package org.wikipedia;
-
-import java.util.concurrent.Executors;
-
-import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
-import org.openstreetmap.josm.tools.Utils;
-
-abstract class WikiSearchTextResultListPanel<T> extends SearchTextResultListPanel<T> {
-
-    protected final Debouncer debouncer = new Debouncer(
-            Executors.newSingleThreadScheduledExecutor(Utils.newThreadFactory("wikipedia-search-%d", Thread.NORM_PRIORITY)));
-
-    public T getSelectedItem() {
-        final T selected = lsResult.getSelectedValue();
-        if (selected != null) {
-            return selected;
-        } else if (!lsResultModel.isEmpty()) {
-            return lsResultModel.getElementAt(0);
-        } else {
-            return null;
-        }
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikidataItemSearchDialog.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikidataItemSearchDialog.java	(revision 32886)
+++ 	(revision )
@@ -1,146 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.wikipedia;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridBagLayout;
-import java.awt.event.ActionEvent;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
-
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
-import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.tools.GBC;
-
-public final class WikidataItemSearchDialog extends ExtendedDialog {
-
-    private final Selector selector;
-    private final AutoCompletingComboBox targetKey;
-    private static final WikidataItemSearchDialog INSTANCE = new WikidataItemSearchDialog();
-
-    private WikidataItemSearchDialog() {
-        super(Main.parent, tr("Search Wikidata items"), new String[]{tr("Add Tag"), tr("Cancel")});
-        this.selector = new Selector();
-        this.selector.setDblClickListener(e -> buttonAction(0, null));
-        this.targetKey = new AutoCompletingComboBox();
-        this.targetKey.setEditable(true);
-        this.targetKey.setSelectedItem(new AutoCompletionListItem("wikidata"));
-
-        final JPanel panel = new JPanel(new GridBagLayout());
-        panel.add(selector, GBC.eop().fill(GBC.BOTH));
-        panel.add(new JLabel(tr("Target key: ")));
-        panel.add(targetKey, GBC.eol().fill(GBC.HORIZONTAL));
-        setContent(panel, false);
-        setPreferredSize(new Dimension(600, 300));
-    }
-
-    /**
-     * Returns the unique instance of {@code MenuItemSearchDialog}.
-     *
-     * @return the unique instance of {@code MenuItemSearchDialog}.
-     */
-    public static synchronized WikidataItemSearchDialog getInstance() {
-        return INSTANCE;
-    }
-
-    @Override
-    public ExtendedDialog showDialog() {
-        initTargetKeys();
-        selector.init();
-        super.showDialog();
-        selector.clearSelection();
-        selector.requestFocus();
-        return this;
-    }
-
-    private void initTargetKeys() {
-        final DataSet editDataSet = Main.getLayerManager().getEditDataSet();
-        if (editDataSet == null) {
-            return;
-        }
-        final Collection<AutoCompletionListItem> keys = new TreeSet<>();
-        // from http://wiki.openstreetmap.org/wiki/Proposed_features/Wikidata#Tagging
-        keys.add(new AutoCompletionListItem("wikidata"));
-        keys.add(new AutoCompletionListItem("operator:wikidata"));
-        keys.add(new AutoCompletionListItem("brand:wikidata"));
-        keys.add(new AutoCompletionListItem("architect:wikidata"));
-        keys.add(new AutoCompletionListItem("artist:wikidata"));
-        keys.add(new AutoCompletionListItem("subject:wikidata"));
-        keys.add(new AutoCompletionListItem("name:etymology:wikidata"));
-        editDataSet.getAutoCompletionManager().getKeys().stream()
-                .filter(v -> v.getValue().contains("wikidata"))
-                .forEach(keys::add);
-        targetKey.setPossibleACItems(keys);
-    }
-
-    @Override
-    protected void buttonAction(int buttonIndex, ActionEvent evt) {
-        super.buttonAction(buttonIndex, evt);
-        if (buttonIndex != 0) {
-            return;
-        }
-        final WikipediaApp.WikidataEntry selected = selector.getSelectedItem();
-        if (selected == null) {
-            return;
-        }
-        final String key = Tag.removeWhiteSpaces(targetKey.getEditor().getItem().toString());
-        final String value = selected.createWikipediaTag().getValue();
-        WikipediaToggleDialog.AddWikipediaTagAction.addTag(new Tag(key, value));
-    }
-
-    private static class Selector extends WikiSearchTextResultListPanel<WikipediaApp.WikidataEntry> {
-
-        Selector() {
-            super();
-            lsResult.setCellRenderer(new DefaultListCellRenderer() {
-                @Override
-                public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
-                    final WikipediaApp.WikidataEntry entry = (WikipediaApp.WikidataEntry) value;
-                    final String labelText = "<html>" + entry.getLabelText();
-                    return super.getListCellRendererComponent(list, labelText, index, isSelected, cellHasFocus);
-                }
-            });
-        }
-
-        @Override
-        protected void filterItems() {
-            final String query = edSearchText.getText();
-            debouncer.debounce(getClass(), () -> {
-                final List<WikipediaApp.WikidataEntry> entries = query == null || query.isEmpty()
-                        ? Collections.emptyList()
-                        : WikipediaApp.getWikidataEntriesForQuery(WikipediaToggleDialog.wikipediaLang.get(), query, Locale.getDefault());
-                GuiHelper.runInEDT(() -> lsResultModel.setItems(entries));
-            }, 200, TimeUnit.MILLISECONDS);
-        }
-    }
-
-    public static class Action extends JosmAction {
-
-        public Action() {
-            super(tr("Search Wikidata items"), "dialogs/wikidata", null,
-                    null, true, "dialogs/search-wikidata-items", false);
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            WikidataItemSearchDialog.getInstance().showDialog();
-        }
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikidataTagCellRenderer.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikidataTagCellRenderer.java	(revision 32886)
+++ 	(revision )
@@ -1,82 +1,0 @@
-package org.wikipedia;
-
-import java.awt.Component;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
-
-import javax.swing.JLabel;
-import javax.swing.JTable;
-import javax.swing.table.DefaultTableCellRenderer;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.tools.Utils;
-
-public class WikidataTagCellRenderer extends DefaultTableCellRenderer {
-
-    final Map<String, CompletableFuture<String>> labelCache = new ConcurrentHashMap<>();
-
-    @Override
-    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
-        if (column != 1
-                || !(value instanceof Map<?, ?> && ((Map<?, ?>) value).size() == 1)) {
-            return null;
-        }
-        final String key = table.getValueAt(row, 0).toString();
-        if (!("wikidata".equals(key) || (key != null && key.endsWith(":wikidata")))) {
-            return null;
-        }
-
-        final String id = ((Map<?, ?>) value).keySet().iterator().next().toString();
-        final JLabel component = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
-        if (WikipediaApp.WIKIDATA_PATTERN.matcher(id).matches()) {
-            return renderValues(Collections.singleton(id), table, component);
-        } else if (id.contains(";")) {
-            final List<String> ids = Arrays.asList(id.split("\\s*;\\s*"));
-            if (ids.stream().allMatch(i -> WikipediaApp.WIKIDATA_PATTERN.matcher(i).matches())) {
-                return renderValues(ids, table, component);
-            }
-        }
-        return null;
-    }
-
-    protected JLabel renderValues(Collection<String> ids, JTable table, JLabel component) {
-
-        ids.forEach(id ->
-                labelCache.computeIfAbsent(id, x ->
-                        CompletableFuture.supplyAsync(() -> WikipediaApp.getLabelForWikidata(x, Locale.getDefault())))
-        );
-
-        final Collection<String> texts = new ArrayList<>(ids.size());
-        for (String id : ids) {
-            if (!labelCache.get(id).isDone()) {
-                labelCache.get(id).thenRun(() -> GuiHelper.runInEDT(table::repaint));
-                return null;
-            }
-            final String label;
-            try {
-                label = labelCache.get(id).get();
-            } catch (InterruptedException | ExecutionException e) {
-                Main.warn("Could not fetch Wikidata label for " + id);
-                Main.warn(e);
-                return null;
-            }
-            if (label == null) {
-                return null;
-            }
-            texts.add(WikipediaApp.WikidataEntry.getLabelText(id, label));
-        }
-        component.setText("<html>" + texts.stream().collect(Collectors.joining("; ")));
-        component.setToolTipText("<html>" + Utils.joinAsHtmlUnorderedList(texts));
-        return component;
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaAddNamesAction.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaAddNamesAction.java	(revision 32886)
+++ 	(revision )
@@ -1,66 +1,0 @@
-// License: GPL. See LICENSE file for details./*
-package org.wikipedia;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.event.ActionEvent;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
-
-public class WikipediaAddNamesAction extends JosmAction {
-
-    public WikipediaAddNamesAction() {
-        super(tr("Add names from Wikipedia"), "dialogs/wikipedia",
-                tr("Fetches interwiki links from Wikipedia in order to add several name tags"),
-                null, true);
-    }
-
-    @Override
-    public void actionPerformed(ActionEvent e) {
-        final WikipediaApp.WikipediaLangArticle wp = WikipediaApp.WikipediaLangArticle.parseTag("wikipedia", getWikipediaValue());
-        List<String[]> tags = new ArrayList<>();
-        WikipediaApp.getInterwikiArticles(wp.lang, wp.article).stream()
-                .filter(this::useWikipediaLangArticle)
-                .map(i -> new String[]{"name:" + i.lang, i.article})
-                .forEach(tags::add);
-        if (Main.isDebugEnabled()) {
-            Main.debug(tags.toString());
-        }
-        AddTagsDialog.addTags(tags.toArray(new String[tags.size()][]), "Wikipedia", getLayerManager().getEditDataSet().getSelected());
-    }
-
-    protected boolean useWikipediaLangArticle(WikipediaApp.WikipediaLangArticle i) {
-        return (!Main.pref.getBoolean("wikipedia.filter-iso-languages", true)
-                || Arrays.asList(Locale.getISOLanguages()).contains(i.lang))
-                && (!Main.pref.getBoolean("wikipedia.filter-same-names", true)
-                || !i.article.equals(getLayerManager().getEditDataSet().getSelected().iterator().next().get("name")));
-    }
-
-    protected String getWikipediaValue() {
-        DataSet ds = getLayerManager().getEditDataSet();
-        if (ds == null || ds.getSelected() == null || ds.getSelected().size() != 1) {
-            return null;
-        } else {
-            return ds.getSelected().iterator().next().get("wikipedia");
-        }
-    }
-
-    @Override
-    protected void updateEnabledState() {
-        setEnabled(getWikipediaValue() != null);
-    }
-
-    @Override
-    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
-        updateEnabledState();
-    }
-}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaApp.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaApp.java	(revision 32886)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaApp.java	(revision 32887)
@@ -12,5 +12,4 @@
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -19,5 +18,4 @@
 import java.util.Objects;
 import java.util.TreeMap;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -31,12 +29,13 @@
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
-import org.openstreetmap.josm.tools.AlphanumComparator;
-import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.HttpClient;
 import org.openstreetmap.josm.tools.Utils;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
+import org.wikipedia.data.WikidataEntry;
+import org.wikipedia.data.WikipediaEntry;
+import org.wikipedia.data.WikipediaLangArticle;
+import org.wikipedia.tools.XPath;
 
 public final class WikipediaApp {
@@ -56,5 +55,5 @@
     }
 
-    static String getSiteUrl(String wikipediaLang) {
+    public static String getSiteUrl(String wikipediaLang) {
         if ("wikidata".equals(wikipediaLang)) {
             return "https://www.wikidata.org";
@@ -64,5 +63,5 @@
     }
 
-    static List<WikipediaEntry> getEntriesFromCoordinates(String wikipediaLang, LatLon min, LatLon max) {
+    public static List<WikipediaEntry> getEntriesFromCoordinates(String wikipediaLang, LatLon min, LatLon max) {
         try {
             // construct url
@@ -99,5 +98,5 @@
     }
 
-    static List<WikidataEntry> getWikidataEntriesForQuery(final String languageForQuery, final String query, final Locale localeForLabels) {
+    public static List<WikidataEntry> getWikidataEntriesForQuery(final String languageForQuery, final String query, final Locale localeForLabels) {
         try {
             final String url = "https://www.wikidata.org/w/api.php" +
@@ -120,5 +119,5 @@
     }
 
-    static List<WikipediaEntry> getEntriesFromCategory(String wikipediaLang, String category, int depth) {
+    public static List<WikipediaEntry> getEntriesFromCategory(String wikipediaLang, String category, int depth) {
         try {
             final String url = "https://tools.wmflabs.org/cats-php/"
@@ -138,5 +137,5 @@
     }
 
-    static List<WikipediaEntry> getEntriesFromClipboard(final String wikipediaLang) {
+    public static List<WikipediaEntry> getEntriesFromClipboard(final String wikipediaLang) {
         return Pattern.compile("[\\n\\r]+")
                 .splitAsStream(ClipboardUtils.getClipboardStringContent())
@@ -145,5 +144,5 @@
     }
 
-    static void updateWIWOSMStatus(String wikipediaLang, List<WikipediaEntry> entries) {
+    public static void updateWIWOSMStatus(String wikipediaLang, List<WikipediaEntry> entries) {
         if (entries.size() > 20) {
             partitionList(entries, 20).forEach(chunk -> updateWIWOSMStatus(wikipediaLang, chunk));
@@ -179,5 +178,5 @@
     }
 
-    static Stream<String> getWikipediaArticles(final String wikipediaLang, OsmPrimitive p) {
+    public static Stream<String> getWikipediaArticles(final String wikipediaLang, OsmPrimitive p) {
         if ("wikidata".equals(wikipediaLang)) {
             return Stream.of(p.get("wikidata")).filter(Objects::nonNull);
@@ -194,5 +193,5 @@
      * Returns a map mapping wikipedia articles to wikidata ids.
      */
-    static Map<String, String> getWikidataForArticles(String wikipediaLang, List<String> articles) {
+    public static Map<String, String> getWikidataForArticles(String wikipediaLang, List<String> articles) {
         if (articles.size() > 50) {
             return partitionList(articles, 50).stream()
@@ -225,5 +224,5 @@
     }
 
-    static List<String> getCategoriesForPrefix(final String wikipediaLang, final String prefix) {
+    public static List<String> getCategoriesForPrefix(final String wikipediaLang, final String prefix) {
         try {
             final String url = getSiteUrl(wikipediaLang) + "/w/api.php"
@@ -247,5 +246,5 @@
     }
 
-    static String getLabelForWikidata(String wikidataId, Locale locale, String ... preferredLanguage) {
+    public static String getLabelForWikidata(String wikidataId, Locale locale, String... preferredLanguage) {
         try {
             final List<WikidataEntry> entry = Collections.singletonList(new WikidataEntry(wikidataId, null, null, null));
@@ -310,5 +309,5 @@
     }
 
-    static Collection<WikipediaLangArticle> getInterwikiArticles(String wikipediaLang, String article) {
+    public static Collection<WikipediaLangArticle> getInterwikiArticles(String wikipediaLang, String article) {
         try {
             final String url = getSiteUrl(wikipediaLang) + "/w/api.php" +
@@ -332,5 +331,5 @@
     }
 
-    static LatLon getCoordinateForArticle(String wikipediaLang, String article) {
+    public static LatLon getCoordinateForArticle(String wikipediaLang, String article) {
         try {
             final String url = getSiteUrl(wikipediaLang) + "/w/api.php" +
@@ -351,153 +350,4 @@
             throw new RuntimeException(ex);
         }
-    }
-
-    static class WikipediaLangArticle {
-
-        final String lang, article;
-
-        public WikipediaLangArticle(String lang, String article) {
-            this.lang = lang;
-            this.article = article;
-        }
-
-        public static WikipediaLangArticle parseFromUrl(String url) {
-            if (url == null) {
-                return null;
-            }
-            // decode URL for nicer value
-            url = Utils.decodeUrl(url);
-            // extract Wikipedia language and
-            final Matcher m = Pattern.compile("(https?:)?//(\\w*)\\.wikipedia\\.org/wiki/(.*)").matcher(url);
-            if (!m.matches()) {
-                return null;
-            }
-            return new WikipediaLangArticle(m.group(2), m.group(3));
-        }
-
-        public static WikipediaLangArticle parseTag(String key, String value) {
-            if (value == null) {
-                return null;
-            } else if (value.startsWith("http")) {
-                //wikipedia=http...
-                return parseFromUrl(value);
-            } else if (value.contains(":")) {
-                //wikipedia=[lang]:[article]
-                //wikipedia:[lang]=[lang]:[article]
-                final String[] item = Utils.decodeUrl(value).split(":", 2);
-                final String article = item[1].replace("_", " ");
-                return new WikipediaLangArticle(item[0], article);
-            } else if (key.startsWith("wikipedia:")) {
-                //wikipedia:[lang]=[lang]:[article]
-                //wikipedia:[lang]=[article]
-                final String lang = key.split(":", 2)[1];
-                final String[] item = Utils.decodeUrl(value).split(":", 2);
-                final String article = item[item.length == 2 ? 1 : 0].replace("_", " ");
-                return new WikipediaLangArticle(lang, article);
-            } else {
-                return null;
-            }
-        }
-
-        @Override
-        public String toString() {
-            return lang + ":" + article;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            final WikipediaLangArticle that = (WikipediaLangArticle) o;
-            return Objects.equals(lang, that.lang) &&
-                    Objects.equals(article, that.article);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(lang, article);
-        }
-    }
-
-    static class WikipediaEntry implements Comparable<WikipediaEntry> {
-
-        final String label;
-        final String wikipediaLang, wikipediaArticle;
-        final LatLon coordinate;
-        private Boolean wiwosmStatus;
-
-        WikipediaEntry(String wikipediaLang, String wikipediaArticle) {
-            this(wikipediaLang, wikipediaArticle, null, null);
-        }
-
-        WikipediaEntry(String wikipediaLang, String wikipediaArticle, String label, LatLon coordinate) {
-            this.label = label;
-            this.wikipediaLang = wikipediaLang;
-            this.wikipediaArticle = wikipediaArticle;
-            this.coordinate = coordinate;
-        }
-
-        protected Tag createWikipediaTag() {
-            return new Tag("wikipedia", wikipediaLang + ":" + wikipediaArticle);
-        }
-
-        public void setWiwosmStatus(Boolean wiwosmStatus) {
-            this.wiwosmStatus = wiwosmStatus;
-        }
-
-        public Boolean getWiwosmStatus() {
-            return wiwosmStatus;
-        }
-
-        public String getBrowserUrl() {
-            return getSiteUrl(wikipediaLang) + "/wiki/" + Utils.encodeUrl(wikipediaArticle.replace(" ", "_"));
-        }
-
-        public String getLabelText() {
-            return wikipediaArticle;
-        }
-
-        @Override
-        public String toString() {
-            return wikipediaArticle;
-        }
-
-        @Override
-        public int compareTo(WikipediaEntry o) {
-            return Comparator
-                    .<WikipediaEntry, String>comparing(x -> x.label, AlphanumComparator.getInstance())
-                    .thenComparing(x -> x.wikipediaArticle, AlphanumComparator.getInstance())
-                    .compare(this, o);
-        }
-    }
-
-    static class WikidataEntry extends WikipediaEntry {
-
-        final String description;
-
-        WikidataEntry(String id, String label, LatLon coordinate, String description) {
-            super("wikidata", id, label, coordinate);
-            this.description = description;
-            ensureValidWikidataId(id);
-        }
-
-        @Override
-        protected Tag createWikipediaTag() {
-            return new Tag("wikidata", wikipediaArticle);
-        }
-
-        @Override
-        public String getLabelText() {
-            final String descriptionInParen = description == null ? "" : (" (" + description + ")");
-            return getLabelText(label, wikipediaArticle + descriptionInParen);
-        }
-
-        static String getLabelText(String bold, String gray) {
-            return Utils.escapeReservedCharactersHTML(bold) + " <span color='gray'>" + Utils.escapeReservedCharactersHTML(gray) + "</span>";
-        }
-    }
-
-    static void ensureValidWikidataId(String id) {
-        CheckParameterUtil.ensureThat(WIKIDATA_PATTERN.matcher(id).matches(), "Invalid Wikidata ID given: " + id);
     }
 
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaCategorySearchDialog.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaCategorySearchDialog.java	(revision 32886)
+++ 	(revision )
@@ -1,64 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.wikipedia;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Dimension;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-
-public final class WikipediaCategorySearchDialog extends ExtendedDialog {
-
-    private final Selector selector;
-    private static final WikipediaCategorySearchDialog INSTANCE = new WikipediaCategorySearchDialog();
-
-    private WikipediaCategorySearchDialog() {
-        super(Main.parent, tr("Search Wikipedia category"), new String[]{tr("Load category"), tr("Cancel")});
-        this.selector = new Selector();
-        this.selector.setDblClickListener(e -> buttonAction(0, null));
-
-        setContent(selector, false);
-        setPreferredSize(new Dimension(600, 300));
-    }
-
-    /**
-     * Returns the unique instance of {@code MenuItemSearchDialog}.
-     *
-     * @return the unique instance of {@code MenuItemSearchDialog}.
-     */
-    public static synchronized WikipediaCategorySearchDialog getInstance() {
-        return INSTANCE;
-    }
-
-    @Override
-    public ExtendedDialog showDialog() {
-        selector.init();
-        super.showDialog();
-        selector.clearSelection();
-        selector.requestFocus();
-        return this;
-    }
-
-    public String getCategory() {
-        return selector.getSelectedItem();
-    }
-
-    private static class Selector extends WikiSearchTextResultListPanel<String> {
-
-        @Override
-        protected void filterItems() {
-            final String query = edSearchText.getText();
-            debouncer.debounce(getClass(), () -> {
-                final List<String> entries = query == null || query.isEmpty()
-                        ? Collections.emptyList()
-                        : WikipediaApp.getCategoriesForPrefix(WikipediaToggleDialog.wikipediaLang.get(), query);
-                GuiHelper.runInEDT(() -> lsResultModel.setItems(entries));
-            }, 200, TimeUnit.MILLISECONDS);
-        }
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaCopyTemplate.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaCopyTemplate.java	(revision 32886)
+++ 	(revision )
@@ -1,109 +1,0 @@
-package org.wikipedia;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.event.ActionEvent;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import javax.swing.JMenuItem;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.Preferences;
-import org.openstreetmap.josm.data.Preferences.pref;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.MainMenu;
-import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
-
-public class WikipediaCopyTemplate {
-
-    private static final List<CoordCopyTemplateEntry> DEFAULT_TEMPLATES = Arrays.asList(
-            new CoordCopyTemplateEntry("{{Coordinate}}", "wikipedia-coordinate", "{{Coordinate|NS={lat}|EW={lon}|type=landmark|region=}}"),
-            new CoordCopyTemplateEntry("{{Coord}}", "wikipedia-coord", "{{Coord|{lat}|{lon}}}"),
-            new CoordCopyTemplateEntry("{{Location dec}}", "wikipedia-location-dec", "{{Location dec|{lat}|{lon}}}"),
-            new CoordCopyTemplateEntry("{{Object location dec}}", "wikipedia-object-location-dec", "{{Object location dec|{lat}|{lon}}}")
-    );
-
-    private static final List<CoordCopyTemplateEntry> TEMPLATE_ENTRIES =
-            Main.pref.getListOfStructs("wikipedia.copytemplates", DEFAULT_TEMPLATES, CoordCopyTemplateEntry.class);
-
-    public WikipediaCopyTemplate() {
-        JosmAction previous = Main.main.menu.copyCoordinates;
-        for (final CoordCopyTemplateEntry templateEntry : TEMPLATE_ENTRIES) {
-            CoordCopyTemplate t = new CoordCopyTemplate(templateEntry);
-            final JMenuItem menu = MainMenu.addAfter(Main.main.menu.editMenu, t, false, previous);
-            menu.setToolTipText(tr("Copies the {0} template to the system clipboard instantiated with the coordinates of the first selected node", templateEntry.name));
-            previous = t;
-        }
-    }
-
-    /**
-     * Class to hold copy templates for serialization using {@link Preferences}.
-     * Public visibility is needed for reflection used in {@link Preferences#getListOfStructs}.
-     */
-    public static class CoordCopyTemplateEntry {
-        @pref
-        public String name;
-        @pref
-        public String id;
-        @pref
-        public String pattern;
-
-        public CoordCopyTemplateEntry() {
-        }
-
-        public CoordCopyTemplateEntry(String name, String id, String pattern) {
-            this.name = name;
-            this.id = id;
-            this.pattern = pattern;
-        }
-    }
-
-    private static class CoordCopyTemplate extends JosmAction {
-
-        protected final String pattern;
-
-        public CoordCopyTemplate(final CoordCopyTemplateEntry entry) {
-            this(tr("Copy {0} template", entry.name), entry.id, entry.pattern);
-        }
-
-        public CoordCopyTemplate(String name, String toolbarId, String pattern) {
-            super(name, "dialogs/wikipedia", null, null, true, toolbarId, true);
-            this.pattern = pattern;
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            Node node = getSelectedNode();
-            if (node == null) {
-                return;
-            }
-            ClipboardUtils.copyString(pattern
-                    .replace("{lat}", Double.toString(node.getCoor().lat()))
-                    .replace("{lon}", Double.toString(node.getCoor().lon())));
-        }
-
-        @Override
-        protected void updateEnabledState() {
-            setEnabled(getSelectedNode() != null);
-        }
-
-        @Override
-        protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
-            updateEnabledState();
-        }
-
-        protected Node getSelectedNode() {
-            DataSet ds = getLayerManager().getEditDataSet();
-            if (ds == null) {
-                return null;
-            } else {
-                return (Node) ds.getSelected().stream().filter(Node.class::isInstance).findFirst().orElse(null);
-            }
-        }
-    }
-}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaPlugin.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaPlugin.java	(revision 32886)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaPlugin.java	(revision 32887)
@@ -7,4 +7,10 @@
 import org.openstreetmap.josm.plugins.Plugin;
 import org.openstreetmap.josm.plugins.PluginInformation;
+import org.wikipedia.actions.FetchWikidataAction;
+import org.wikipedia.actions.WikipediaAddNamesAction;
+import org.wikipedia.actions.WikipediaCopyTemplate;
+import org.wikipedia.gui.WikidataItemSearchDialog;
+import org.wikipedia.gui.WikidataTagCellRenderer;
+import org.wikipedia.gui.WikipediaToggleDialog;
 
 public class WikipediaPlugin extends Plugin {
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaToggleDialog.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/WikipediaToggleDialog.java	(revision 32886)
+++ 	(revision )
@@ -1,387 +1,0 @@
-// License: GPL. See LICENSE file for details.
-package org.wikipedia;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import javax.swing.AbstractAction;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.DefaultListModel;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPopupMenu;
-import javax.swing.SwingWorker;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.search.SearchAction;
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
-import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
-import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
-import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
-import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
-import org.openstreetmap.josm.data.preferences.StringProperty;
-import org.openstreetmap.josm.gui.SideButton;
-import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
-import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
-import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.LanguageInfo;
-import org.openstreetmap.josm.tools.OpenBrowser;
-import org.wikipedia.WikipediaApp.WikipediaEntry;
-
-public class WikipediaToggleDialog extends ToggleDialog implements ActiveLayerChangeListener, DataSetListenerAdapter.Listener {
-
-    public WikipediaToggleDialog() {
-        super(tr("Wikipedia"), "wikipedia", tr("Fetch Wikipedia articles with coordinates"), null, 150);
-        createLayout(list, true, Arrays.asList(
-                new SideButton(new WikipediaLoadCoordinatesAction(false)),
-                new SideButton(new WikipediaLoadCoordinatesAction(true)),
-                new SideButton(new WikipediaLoadCategoryAction()),
-                new SideButton(new PasteWikipediaArticlesAction()),
-                new SideButton(new AddWikipediaTagAction(list)),
-                new SideButton(new WikipediaSettingsAction(), false)));
-        updateTitle();
-    }
-    /** A string describing the context (use-case) for determining the dialog title */
-    String titleContext = null;
-    static final StringProperty wikipediaLang = new StringProperty("wikipedia.lang", LanguageInfo.getJOSMLocaleCode().substring(0, 2));
-    final Set<String> articles = new HashSet<>();
-    final DefaultListModel<WikipediaEntry> model = new DefaultListModel<>();
-    final JList<WikipediaEntry> list = new JList<WikipediaEntry>(model) {
-
-        {
-            setToolTipText(tr("Double click on item to search for object with article name (and center coordinate)"));
-            addMouseListener(new MouseAdapter() {
-
-                @Override
-                public void mouseClicked(MouseEvent e) {
-                    if (e.getClickCount() == 2 && getSelectedValue() != null) {
-                        final WikipediaEntry entry = getSelectedValue();
-                        if (entry.coordinate != null) {
-                            BoundingXYVisitor bbox = new BoundingXYVisitor();
-                            bbox.visit(entry.coordinate);
-                            Main.map.mapView.zoomTo(bbox);
-                        }
-                        final String search = Optional.ofNullable(entry.label).orElse(entry.wikipediaArticle).replaceAll("\\(.*\\)", "");
-                        SearchAction.search(search, SearchAction.SearchMode.replace);
-                    }
-                }
-            });
-
-            setCellRenderer(new DefaultListCellRenderer() {
-
-                @Override
-                public JLabel getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
-                    final WikipediaEntry entry = (WikipediaEntry) value;
-                    final String labelText = "<html>" + entry.getLabelText();
-                    final JLabel label = (JLabel) super.getListCellRendererComponent(list, labelText, index, isSelected, cellHasFocus);
-                    if (entry.getWiwosmStatus() != null && entry.getWiwosmStatus()) {
-                        label.setIcon(ImageProvider.getIfAvailable("misc", "grey_check"));
-                        label.setToolTipText(/* I18n: WIWOSM server already links Wikipedia article to object/s */ tr("Available via WIWOSM server"));
-                    } else if (articles.contains(entry.wikipediaArticle)) {
-                        label.setIcon(ImageProvider.getIfAvailable("misc", "green_check"));
-                        label.setToolTipText(/* I18n: object/s from dataset contain link to Wikipedia article */ tr("Available in local dataset"));
-                    } else {
-                        label.setToolTipText(tr("Not linked yet"));
-                    }
-                    return label;
-                }
-            });
-
-            final JPopupMenu popupMenu = new JPopupMenu();
-            popupMenu.add(new OpenWikipediaArticleAction());
-            popupMenu.add(new ZoomToWikipediaArticleAction());
-            setComponentPopupMenu(popupMenu);
-        }
-    };
-
-    private void updateTitle() {
-        final String lang = getLanguageOfFirstItem();
-        final String host = WikipediaApp.getSiteUrl(lang).split("/+")[1];
-        if (titleContext == null) {
-            setTitle(host);
-        } else {
-            setTitle(tr("{0}: {1}", host, titleContext));
-        }
-    }
-
-    private String getLanguageOfFirstItem() {
-        try {
-            return list.getModel().getElementAt(0).wikipediaLang;
-        } catch (ArrayIndexOutOfBoundsException ignore) {
-            return wikipediaLang.get();
-        }
-    }
-
-    class WikipediaLoadCoordinatesAction extends AbstractAction {
-
-        private final boolean wikidata;
-
-        public WikipediaLoadCoordinatesAction(boolean wikidata) {
-            super(wikidata ? tr("Wikidata") : tr("Coordinates"));
-            this.wikidata = wikidata;
-            new ImageProvider("dialogs", wikidata ? "wikidata" : "wikipedia").getResource().attachImageIcon(this, true);
-            putValue(SHORT_DESCRIPTION, wikidata
-                    ? tr("Fetches all coordinates from Wikidata in the current view")
-                    : tr("Fetches all coordinates from Wikipedia in the current view"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            try {
-                // determine bbox
-                final LatLon min = Main.map.mapView.getLatLon(0, Main.map.mapView.getHeight());
-                final LatLon max = Main.map.mapView.getLatLon(Main.map.mapView.getWidth(), 0);
-                // add entries to list model
-                titleContext = tr("coordinates");
-                updateTitle();
-                new UpdateWikipediaArticlesSwingWorker() {
-
-                    @Override
-                    List<WikipediaEntry> getEntries() {
-                        return WikipediaApp.getEntriesFromCoordinates(
-                                wikidata ? "wikidata" : wikipediaLang.get(), min, max);
-                    }
-                }.execute();
-            } catch (Exception ex) {
-                throw new RuntimeException(ex);
-            }
-        }
-    }
-
-    abstract class UpdateWikipediaArticlesSwingWorker extends SwingWorker<Void, WikipediaEntry> {
-
-        abstract List<WikipediaEntry> getEntries();
-
-        @Override
-        protected Void doInBackground() throws Exception {
-            final List<WikipediaEntry> entries = getEntries();
-            entries.sort(null);
-            publish(entries.toArray(new WikipediaEntry[entries.size()]));
-            WikipediaApp.partitionList(entries, 20).forEach(chunk -> {
-                WikipediaApp.updateWIWOSMStatus(chunk.get(0).wikipediaLang, chunk);
-                list.repaint();
-            });
-            return null;
-        }
-
-        @Override
-        protected void process(List<WikipediaEntry> chunks) {
-            model.clear();
-            chunks.forEach(model::addElement);
-            updateTitle();
-            updateWikipediaArticles();
-        }
-
-    }
-
-    class WikipediaLoadCategoryAction extends AbstractAction {
-
-        public WikipediaLoadCategoryAction() {
-            super(tr("Category"));
-            new ImageProvider("data", "sequence").getResource().attachImageIcon(this, true);
-            putValue(SHORT_DESCRIPTION, tr("Fetches a list of all Wikipedia articles of a category"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            final WikipediaCategorySearchDialog categorySearchDialog = WikipediaCategorySearchDialog.getInstance();
-            categorySearchDialog.showDialog();
-            if (categorySearchDialog.getValue() != 1) {
-                return;
-            }
-            final String category = categorySearchDialog.getCategory();
-            if (category == null) {
-                return;
-            }
-
-            titleContext = category;
-            updateTitle();
-
-            new UpdateWikipediaArticlesSwingWorker() {
-                @Override
-                List<WikipediaEntry> getEntries() {
-                    return WikipediaApp.getEntriesFromCategory(
-                            wikipediaLang.get(), category, Main.pref.getInteger("wikipedia.depth", 3));
-                }
-            }.execute();
-        }
-    }
-
-    class PasteWikipediaArticlesAction extends AbstractAction {
-
-        public PasteWikipediaArticlesAction() {
-            super(tr("Clipboard"));
-            new ImageProvider("paste").getResource().attachImageIcon(this, true);
-            putValue(SHORT_DESCRIPTION, tr("Pastes Wikipedia articles from the system clipboard"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            titleContext = tr("clipboard");
-            updateTitle();
-            new UpdateWikipediaArticlesSwingWorker() {
-
-                @Override
-                List<WikipediaEntry> getEntries() {
-                    return WikipediaApp.getEntriesFromClipboard(wikipediaLang.get());
-                }
-            }.execute();
-        }
-    }
-
-    class OpenWikipediaArticleAction extends AbstractAction {
-
-        public OpenWikipediaArticleAction() {
-            super(tr("Open Article"));
-            new ImageProvider("browser").getResource().attachImageIcon(this);
-            putValue(SHORT_DESCRIPTION, tr("Opens the Wikipedia article of the selected item in a browser"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            if (list.getSelectedValue() != null) {
-                final String url = list.getSelectedValue().getBrowserUrl();
-                Main.info("Wikipedia: opening " + url);
-                OpenBrowser.displayUrl(url);
-            }
-        }
-    }
-
-    class WikipediaSettingsAction extends AbstractAction {
-
-        public WikipediaSettingsAction() {
-            super(tr("Language"));
-            new ImageProvider("dialogs/settings").getResource().attachImageIcon(this, true);
-            putValue(SHORT_DESCRIPTION, tr("Sets the default language for the Wikipedia articles"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            String lang = JOptionPane.showInputDialog(
-                    Main.parent,
-                    tr("Enter the Wikipedia language"),
-                    wikipediaLang.get());
-            if (lang != null) {
-                wikipediaLang.put(lang);
-                updateTitle();
-                updateWikipediaArticles();
-            }
-        }
-    }
-
-    static class AddWikipediaTagAction extends AbstractAction {
-
-        private final JList<WikipediaEntry> list;
-
-        public AddWikipediaTagAction(JList<WikipediaEntry> list) {
-            super(tr("Add Tag"));
-            this.list = list;
-            new ImageProvider("pastetags").getResource().attachImageIcon(this, true);
-            putValue(SHORT_DESCRIPTION, tr("Adds a ''wikipedia'' tag corresponding to this article to the selected objects"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            addTag(list.getSelectedValue());
-        }
-
-        static void addTag(WikipediaEntry entry) {
-            if (entry == null) {
-                return;
-            }
-            addTag(entry.createWikipediaTag());
-        }
-
-        static void addTag(Tag tag) {
-            if (tag == null) {
-                return;
-            }
-            final Collection<OsmPrimitive> selected = Main.getLayerManager().getEditDataSet().getSelected();
-            if (!GuiUtils.confirmOverwrite(tag.getKey(), tag.getValue(), selected)) {
-                return;
-            }
-            ChangePropertyCommand cmd = new ChangePropertyCommand(
-                    selected,
-                    tag.getKey(), tag.getValue());
-            Main.main.undoRedo.add(cmd);
-            Main.worker.submit(new FetchWikidataAction.Fetcher(selected));
-        }
-    }
-
-    class ZoomToWikipediaArticleAction extends AbstractAction {
-
-        ZoomToWikipediaArticleAction() {
-            super(tr("Zoom to selection"));
-            new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this);
-            putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            final WikipediaEntry entry = list.getSelectedValue();
-            if (entry == null) {
-                return;
-            }
-            final LatLon latLon = entry.coordinate != null
-                    ? entry.coordinate
-                    : WikipediaApp.getCoordinateForArticle(entry.wikipediaLang, entry.wikipediaArticle);
-            if (latLon == null) {
-                return;
-            }
-            Main.map.mapView.zoomTo(latLon);
-        }
-    }
-
-    protected void updateWikipediaArticles() {
-        final String language = getLanguageOfFirstItem();
-        articles.clear();
-        if (Main.main != null && Main.getLayerManager().getEditDataSet() != null) {
-            Main.getLayerManager().getEditDataSet().allPrimitives().stream()
-                    .flatMap(p -> WikipediaApp.getWikipediaArticles(language, p))
-                    .forEach(articles::add);
-        }
-    }
-
-    private final DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this);
-
-    @Override
-    public void showNotify() {
-        DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED);
-        Main.getLayerManager().addActiveLayerChangeListener(this);
-        updateWikipediaArticles();
-    }
-
-    @Override
-    public void hideNotify() {
-        DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter);
-        Main.getLayerManager().removeActiveLayerChangeListener(this);
-        articles.clear();
-    }
-
-    @Override
-    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
-        updateWikipediaArticles();
-        list.repaint();
-    }
-
-    @Override
-    public void processDatasetEvent(AbstractDatasetChangedEvent event) {
-        updateWikipediaArticles();
-        list.repaint();
-    }
-}
Index: plications/editors/josm/plugins/wikipedia/src/org/wikipedia/XPath.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/XPath.java	(revision 32886)
+++ 	(revision )
@@ -1,139 +1,0 @@
-// License: GPL. See LICENSE file for details./*
-package org.wikipedia;
-
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-
-import javax.xml.namespace.NamespaceContext;
-import javax.xml.namespace.QName;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpression;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-import javax.xml.xpath.XPathFunctionResolver;
-import javax.xml.xpath.XPathVariableResolver;
-import java.util.AbstractList;
-import java.util.Collection;
-
-public class XPath implements javax.xml.xpath.XPath {
-    private final javax.xml.xpath.XPath xPath;
-    private static final XPath INSTANCE = new XPath(XPathFactory.newInstance().newXPath());
-
-    private XPath(javax.xml.xpath.XPath xPath) {
-        this.xPath = xPath;
-    }
-
-    public static XPath getInstance() {
-        return INSTANCE;
-    }
-
-    public static class UncheckedXPathExpressionException extends RuntimeException {
-        public UncheckedXPathExpressionException(Throwable cause) {
-            super(cause);
-        }
-    }
-
-    public String evaluateString(String expression, Object item) throws UncheckedXPathExpressionException {
-        try {
-            return (String) xPath.evaluate(expression, item, XPathConstants.STRING);
-        } catch (XPathExpressionException e) {
-            throw new UncheckedXPathExpressionException(e);
-        }
-    }
-
-    public double evaluateDouble(String expression, Object item) throws UncheckedXPathExpressionException {
-        try {
-            return ((Number) xPath.evaluate(expression, item, XPathConstants.NUMBER)).doubleValue();
-        } catch (XPathExpressionException e) {
-            throw new UncheckedXPathExpressionException(e);
-        }
-    }
-
-    public Node evaluateNode(String expression, Object item) throws UncheckedXPathExpressionException {
-        try {
-            return (Node) evaluate(expression, item, XPathConstants.NODE);
-        } catch (XPathExpressionException e) {
-            throw new UncheckedXPathExpressionException(e);
-        }
-    }
-
-    public Collection<Node> evaluateNodes(String expression, Object item) throws UncheckedXPathExpressionException {
-        try {
-            final NodeList nodes = (NodeList) evaluate(expression, item, XPathConstants.NODESET);
-            return new AbstractList<Node>() {
-                @Override
-                public Node get(int index) {
-                    return nodes.item(index);
-                }
-
-                @Override
-                public int size() {
-                    return nodes.getLength();
-                }
-            };
-        } catch (XPathExpressionException e) {
-            throw new UncheckedXPathExpressionException(e);
-        }
-    }
-
-    @Override
-    public void reset() {
-        xPath.reset();
-    }
-
-    @Override
-    public void setXPathVariableResolver(XPathVariableResolver resolver) {
-        xPath.setXPathVariableResolver(resolver);
-    }
-
-    @Override
-    public XPathVariableResolver getXPathVariableResolver() {
-        return xPath.getXPathVariableResolver();
-    }
-
-    @Override
-    public void setXPathFunctionResolver(XPathFunctionResolver resolver) {
-        xPath.setXPathFunctionResolver(resolver);
-    }
-
-    @Override
-    public XPathFunctionResolver getXPathFunctionResolver() {
-        return xPath.getXPathFunctionResolver();
-    }
-
-    @Override
-    public void setNamespaceContext(NamespaceContext nsContext) {
-        xPath.setNamespaceContext(nsContext);
-    }
-
-    @Override
-    public NamespaceContext getNamespaceContext() {
-        return xPath.getNamespaceContext();
-    }
-
-    @Override
-    public XPathExpression compile(String expression) throws XPathExpressionException {
-        return xPath.compile(expression);
-    }
-
-    @Override
-    public Object evaluate(String expression, InputSource source, QName returnType) throws XPathExpressionException {
-        return xPath.evaluate(expression, source, returnType);
-    }
-
-    @Override
-    public String evaluate(String expression, InputSource source) throws XPathExpressionException {
-        return xPath.evaluate(expression, source);
-    }
-
-    @Override
-    public Object evaluate(String expression, Object item, QName returnType) throws XPathExpressionException {
-        return xPath.evaluate(expression, item, returnType);
-    }
-
-    @Override
-    public String evaluate(String expression, Object item) throws XPathExpressionException {
-        return xPath.evaluate(expression, item);
-    }
-}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/FetchWikidataAction.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/FetchWikidataAction.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/FetchWikidataAction.java	(revision 32887)
@@ -0,0 +1,176 @@
+// License: GPL. See LICENSE file for details./*
+package org.wikipedia.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.Notification;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.tools.MultiMap;
+import org.openstreetmap.josm.tools.Utils;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.data.WikipediaLangArticle;
+import org.wikipedia.gui.GuiUtils;
+
+public class FetchWikidataAction extends JosmAction {
+
+    public FetchWikidataAction() {
+        super(tr("Fetch Wikidata IDs"), "dialogs/wikidata",
+                tr("Fetch Wikidata IDs using the ''wikipedia'' tag"), null, true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        DataSet ds = getLayerManager().getEditDataSet();
+        if (ds == null) {
+            return;
+        }
+        Main.worker.submit(new Fetcher(ds.getSelected()));
+    }
+
+    public static class Fetcher extends PleaseWaitRunnable {
+        private final Collection<? extends OsmPrimitive> selection;
+        private boolean canceled = false;
+        private final List<Command> commands = new ArrayList<>();
+        private final Collection<WikipediaLangArticle> notFound = new ArrayList<>();
+
+        public Fetcher(Collection<? extends OsmPrimitive> selection) {
+            super(tr("Fetching Wikidata IDs"));
+            this.selection = selection;
+        }
+
+        @Override
+        protected void cancel() {
+            canceled = true;
+        }
+
+        @Override
+        protected void realRun() {
+            final Map<String, PrimitivesWithWikipedia> wikipediaByLanguage = getLanguageToArticlesMap(selection);
+            getProgressMonitor().setTicksCount(wikipediaByLanguage.keySet().size());
+            for (final Map.Entry<String, PrimitivesWithWikipedia> i : wikipediaByLanguage.entrySet()) {
+                if (canceled) {
+                    break;
+                }
+                final PrimitivesWithWikipedia fetcher = i.getValue();
+                fetcher.updateWikidataIds(getProgressMonitor().createSubTaskMonitor(1, false));
+                final Command command = fetcher.getCommand();
+                if (command != null) {
+                    commands.add(command);
+                }
+                notFound.addAll(fetcher.getNotFound());
+            }
+        }
+
+        static Map<String, PrimitivesWithWikipedia> getLanguageToArticlesMap(final Iterable<? extends OsmPrimitive> selection) {
+            final Map<String, PrimitivesWithWikipedia> r = new HashMap<>();
+            for (final OsmPrimitive i : selection) {
+                final WikipediaLangArticle tag = WikipediaLangArticle.parseTag("wikipedia", i.get("wikipedia"));
+                if (tag != null) {
+                    if (!r.containsKey(tag.lang)) {
+                        r.put(tag.lang, new PrimitivesWithWikipedia(tag.lang));
+                    }
+                    r.get(tag.lang).put(i, tag.article);
+                }
+            }
+            return r;
+        }
+
+        @Override
+        protected void finish() {
+            if (!canceled && !commands.isEmpty()) {
+                Main.main.undoRedo.add(commands.size() == 1 ? commands.get(0) : new SequenceCommand(tr("Add Wikidata"), commands));
+            }
+            if (!canceled && !notFound.isEmpty()) {
+                new Notification(tr("No Wikidata ID found for: {0}", Utils.joinAsHtmlUnorderedList(notFound)))
+                        .setIcon(JOptionPane.WARNING_MESSAGE)
+                        .setDuration(Notification.TIME_LONG)
+                        .show();
+            }
+        }
+    }
+
+    private static class PrimitivesWithWikipedia {
+        final String lang;
+        final MultiMap<String, OsmPrimitive> byArticle = new MultiMap<>();
+        final List<Command> commands = new ArrayList<>();
+        final List<WikipediaLangArticle> notFound = new ArrayList<>();
+
+        PrimitivesWithWikipedia(String lang) {
+            this.lang = lang;
+        }
+
+        public void put(OsmPrimitive key, String wikipedia) {
+            byArticle.put(wikipedia, key);
+        }
+
+        void updateWikidataIds(ProgressMonitor monitor) {
+            final int size = byArticle.keySet().size();
+            monitor.beginTask(trn(
+                    "Fetching {0} Wikidata ID for language ''{1}''",
+                    "Fetching {0} Wikidata IDs for language ''{1}''", size, size, lang));
+            final Map<String, String> wikidataByWikipedia = WikipediaApp.getWikidataForArticles(lang, new ArrayList<>(byArticle.keySet()));
+            ConditionalOptionPaneUtil.startBulkOperation(GuiUtils.PREF_OVERWRITE);
+            for (Map.Entry<String, Set<OsmPrimitive>> i : byArticle.entrySet()) {
+                final String wikipedia = i.getKey();
+                final String wikidata = wikidataByWikipedia.get(wikipedia);
+                if (wikidata != null) {
+                    if (GuiUtils.confirmOverwrite("wikidata", wikidata, i.getValue())) {
+                        commands.add(new ChangePropertyCommand(i.getValue(), "wikidata", wikidata));
+                    }
+                } else {
+                    final WikipediaLangArticle article = new WikipediaLangArticle(lang, wikipedia);
+                    Main.warn(tr("No Wikidata ID found for: {0}", article));
+                    notFound.add(article);
+                }
+            }
+            ConditionalOptionPaneUtil.endBulkOperation(GuiUtils.PREF_OVERWRITE);
+            monitor.finishTask();
+        }
+
+        public Command getCommand() {
+            return commands.isEmpty()
+                    ? null
+                    : new SequenceCommand(tr("Add Wikidata for language ''{0}''", lang), commands);
+        }
+
+        List<WikipediaLangArticle> getNotFound() {
+            return notFound;
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        updateEnabledStateOnCurrentSelection();
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        for (final OsmPrimitive i : selection) {
+            if (i.hasKey("wikipedia")) {
+                setEnabled(true);
+                return;
+            }
+        }
+        setEnabled(false);
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/WikipediaAddNamesAction.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/WikipediaAddNamesAction.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/WikipediaAddNamesAction.java	(revision 32887)
@@ -0,0 +1,68 @@
+// License: GPL. See LICENSE file for details./*
+package org.wikipedia.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.data.WikipediaLangArticle;
+
+public class WikipediaAddNamesAction extends JosmAction {
+
+    public WikipediaAddNamesAction() {
+        super(tr("Add names from Wikipedia"), "dialogs/wikipedia",
+                tr("Fetches interwiki links from Wikipedia in order to add several name tags"),
+                null, true);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        final WikipediaLangArticle wp = WikipediaLangArticle.parseTag("wikipedia", getWikipediaValue());
+        List<String[]> tags = new ArrayList<>();
+        WikipediaApp.getInterwikiArticles(wp.lang, wp.article).stream()
+                .filter(this::useWikipediaLangArticle)
+                .map(i -> new String[]{"name:" + i.lang, i.article})
+                .forEach(tags::add);
+        if (Main.isDebugEnabled()) {
+            Main.debug(tags.toString());
+        }
+        AddTagsDialog.addTags(tags.toArray(new String[tags.size()][]), "Wikipedia", getLayerManager().getEditDataSet().getSelected());
+    }
+
+    private boolean useWikipediaLangArticle(WikipediaLangArticle i) {
+        return (!Main.pref.getBoolean("wikipedia.filter-iso-languages", true)
+                || Arrays.asList(Locale.getISOLanguages()).contains(i.lang))
+                && (!Main.pref.getBoolean("wikipedia.filter-same-names", true)
+                || !i.article.equals(getLayerManager().getEditDataSet().getSelected().iterator().next().get("name")));
+    }
+
+    private String getWikipediaValue() {
+        DataSet ds = getLayerManager().getEditDataSet();
+        if (ds == null || ds.getSelected() == null || ds.getSelected().size() != 1) {
+            return null;
+        } else {
+            return ds.getSelected().iterator().next().get("wikipedia");
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        setEnabled(getWikipediaValue() != null);
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        updateEnabledState();
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/WikipediaCopyTemplate.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/WikipediaCopyTemplate.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/actions/WikipediaCopyTemplate.java	(revision 32887)
@@ -0,0 +1,110 @@
+package org.wikipedia.actions;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JMenuItem;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.Preferences;
+import org.openstreetmap.josm.data.Preferences.pref;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.MainMenu;
+import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
+
+public class WikipediaCopyTemplate {
+
+    private static final List<CoordCopyTemplateEntry> DEFAULT_TEMPLATES = Arrays.asList(
+            new CoordCopyTemplateEntry("{{Coordinate}}", "wikipedia-coordinate", "{{Coordinate|NS={lat}|EW={lon}|type=landmark|region=}}"),
+            new CoordCopyTemplateEntry("{{Coord}}", "wikipedia-coord", "{{Coord|{lat}|{lon}}}"),
+            new CoordCopyTemplateEntry("{{Location dec}}", "wikipedia-location-dec", "{{Location dec|{lat}|{lon}}}"),
+            new CoordCopyTemplateEntry("{{Object location dec}}", "wikipedia-object-location-dec", "{{Object location dec|{lat}|{lon}}}")
+    );
+
+    private static final List<CoordCopyTemplateEntry> TEMPLATE_ENTRIES =
+            Main.pref.getListOfStructs("wikipedia.copytemplates", DEFAULT_TEMPLATES, CoordCopyTemplateEntry.class);
+
+    public WikipediaCopyTemplate() {
+        JosmAction previous = Main.main.menu.copyCoordinates;
+        for (final CoordCopyTemplateEntry templateEntry : TEMPLATE_ENTRIES) {
+            CoordCopyTemplate t = new CoordCopyTemplate(templateEntry);
+            final JMenuItem menu = MainMenu.addAfter(Main.main.menu.editMenu, t, false, previous);
+            menu.setToolTipText(tr("Copies the {0} template to the system clipboard instantiated with the coordinates of the first selected node", templateEntry.name));
+            previous = t;
+        }
+    }
+
+    /**
+     * Class to hold copy templates for serialization using {@link Preferences}.
+     * Public visibility is needed for reflection used in {@link Preferences#getListOfStructs}.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class CoordCopyTemplateEntry {
+        @pref
+        public String name;
+        @pref
+        public String id;
+        @pref
+        public String pattern;
+
+        public CoordCopyTemplateEntry() {
+        }
+
+        public CoordCopyTemplateEntry(String name, String id, String pattern) {
+            this.name = name;
+            this.id = id;
+            this.pattern = pattern;
+        }
+    }
+
+    private static class CoordCopyTemplate extends JosmAction {
+
+        protected final String pattern;
+
+        CoordCopyTemplate(final CoordCopyTemplateEntry entry) {
+            this(tr("Copy {0} template", entry.name), entry.id, entry.pattern);
+        }
+
+        CoordCopyTemplate(String name, String toolbarId, String pattern) {
+            super(name, "dialogs/wikipedia", null, null, true, toolbarId, true);
+            this.pattern = pattern;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            Node node = getSelectedNode();
+            if (node == null) {
+                return;
+            }
+            ClipboardUtils.copyString(pattern
+                    .replace("{lat}", Double.toString(node.getCoor().lat()))
+                    .replace("{lon}", Double.toString(node.getCoor().lon())));
+        }
+
+        @Override
+        protected void updateEnabledState() {
+            setEnabled(getSelectedNode() != null);
+        }
+
+        @Override
+        protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+            updateEnabledState();
+        }
+
+        Node getSelectedNode() {
+            DataSet ds = getLayerManager().getEditDataSet();
+            if (ds == null) {
+                return null;
+            } else {
+                return (Node) ds.getSelected().stream().filter(Node.class::isInstance).findFirst().orElse(null);
+            }
+        }
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikidataEntry.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikidataEntry.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikidataEntry.java	(revision 32887)
@@ -0,0 +1,37 @@
+package org.wikipedia.data;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.Utils;
+import org.wikipedia.WikipediaApp;
+
+public class WikidataEntry extends WikipediaEntry {
+
+    public final String description;
+
+    public WikidataEntry(String id, String label, LatLon coordinate, String description) {
+        super("wikidata", id, label, coordinate);
+        this.description = description;
+        ensureValidWikidataId(id);
+    }
+
+    @Override
+    public Tag createWikipediaTag() {
+        return new Tag("wikidata", wikipediaArticle);
+    }
+
+    @Override
+    public String getLabelText() {
+        final String descriptionInParen = description == null ? "" : (" (" + description + ")");
+        return getLabelText(label, wikipediaArticle + descriptionInParen);
+    }
+
+    public static String getLabelText(String bold, String gray) {
+        return Utils.escapeReservedCharactersHTML(bold) + " <span color='gray'>" + Utils.escapeReservedCharactersHTML(gray) + "</span>";
+    }
+
+    private static void ensureValidWikidataId(String id) {
+        CheckParameterUtil.ensureThat(WikipediaApp.WIKIDATA_PATTERN.matcher(id).matches(), "Invalid Wikidata ID given: " + id);
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikipediaEntry.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikipediaEntry.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikipediaEntry.java	(revision 32887)
@@ -0,0 +1,61 @@
+package org.wikipedia.data;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.tools.AlphanumComparator;
+import org.openstreetmap.josm.tools.Utils;
+import org.wikipedia.WikipediaApp;
+
+import java.util.Comparator;
+
+public class WikipediaEntry implements Comparable<WikipediaEntry> {
+
+    public final String label;
+    public final String wikipediaLang, wikipediaArticle;
+    public final LatLon coordinate;
+    private Boolean wiwosmStatus;
+
+    public WikipediaEntry(String wikipediaLang, String wikipediaArticle) {
+        this(wikipediaLang, wikipediaArticle, null, null);
+    }
+
+    public WikipediaEntry(String wikipediaLang, String wikipediaArticle, String label, LatLon coordinate) {
+        this.label = label;
+        this.wikipediaLang = wikipediaLang;
+        this.wikipediaArticle = wikipediaArticle;
+        this.coordinate = coordinate;
+    }
+
+    public Tag createWikipediaTag() {
+        return new Tag("wikipedia", wikipediaLang + ":" + wikipediaArticle);
+    }
+
+    public void setWiwosmStatus(Boolean wiwosmStatus) {
+        this.wiwosmStatus = wiwosmStatus;
+    }
+
+    public Boolean getWiwosmStatus() {
+        return wiwosmStatus;
+    }
+
+    public String getBrowserUrl() {
+        return WikipediaApp.getSiteUrl(wikipediaLang) + "/wiki/" + Utils.encodeUrl(wikipediaArticle.replace(" ", "_"));
+    }
+
+    public String getLabelText() {
+        return wikipediaArticle;
+    }
+
+    @Override
+    public String toString() {
+        return wikipediaArticle;
+    }
+
+    @Override
+    public int compareTo(WikipediaEntry o) {
+        return Comparator
+                .<WikipediaEntry, String>comparing(x -> x.label, AlphanumComparator.getInstance())
+                .thenComparing(x -> x.wikipediaArticle, AlphanumComparator.getInstance())
+                .compare(this, o);
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikipediaLangArticle.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikipediaLangArticle.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/data/WikipediaLangArticle.java	(revision 32887)
@@ -0,0 +1,75 @@
+package org.wikipedia.data;
+
+import org.openstreetmap.josm.tools.Utils;
+import org.wikipedia.WikipediaApp;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class WikipediaLangArticle {
+
+    public final String lang, article;
+
+    public WikipediaLangArticle(String lang, String article) {
+        this.lang = lang;
+        this.article = article;
+    }
+
+    public static WikipediaLangArticle parseFromUrl(String url) {
+        if (url == null) {
+            return null;
+        }
+        // decode URL for nicer value
+        url = Utils.decodeUrl(url);
+        // extract Wikipedia language and
+        final Matcher m = Pattern.compile("(https?:)?//(\\w*)\\.wikipedia\\.org/wiki/(.*)").matcher(url);
+        if (!m.matches()) {
+            return null;
+        }
+        return new WikipediaLangArticle(m.group(2), m.group(3));
+    }
+
+    public static WikipediaLangArticle parseTag(String key, String value) {
+        if (value == null) {
+            return null;
+        } else if (value.startsWith("http")) {
+            //wikipedia=http...
+            return parseFromUrl(value);
+        } else if (value.contains(":")) {
+            //wikipedia=[lang]:[article]
+            //wikipedia:[lang]=[lang]:[article]
+            final String[] item = Utils.decodeUrl(value).split(":", 2);
+            final String article = item[1].replace("_", " ");
+            return new WikipediaLangArticle(item[0], article);
+        } else if (key.startsWith("wikipedia:")) {
+            //wikipedia:[lang]=[lang]:[article]
+            //wikipedia:[lang]=[article]
+            final String lang = key.split(":", 2)[1];
+            final String[] item = Utils.decodeUrl(value).split(":", 2);
+            final String article = item[item.length == 2 ? 1 : 0].replace("_", " ");
+            return new WikipediaLangArticle(lang, article);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return lang + ":" + article;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final WikipediaLangArticle that = (WikipediaLangArticle) o;
+        return Objects.equals(lang, that.lang) &&
+                Objects.equals(article, that.article);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(lang, article);
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/GuiUtils.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/GuiUtils.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/GuiUtils.java	(revision 32887)
@@ -0,0 +1,47 @@
+// License: GPL. See LICENSE file for details./*
+package org.wikipedia.gui;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.tools.AlphanumComparator;
+import org.openstreetmap.josm.tools.Utils;
+
+import javax.swing.JOptionPane;
+import java.util.Collection;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+public class GuiUtils {
+
+    public static final String PREF_OVERWRITE = "wikipedia.overwrite-tag";
+
+    public static boolean confirmOverwrite(final String key, final String newValue, final Collection<OsmPrimitive> primitives) {
+        final SortedSet<String> existingValues = primitives.stream()
+                .map(x -> x.get(key))
+                .filter(x -> x != null && !newValue.equals(x))
+                .collect(Collectors.toCollection(() -> new TreeSet<>(AlphanumComparator.getInstance())));
+
+        if (existingValues.isEmpty()) {
+            return true;
+        }
+        final Boolean r = GuiHelper.runInEDTAndWaitAndReturn(() ->
+                ConditionalOptionPaneUtil.showConfirmationDialog(PREF_OVERWRITE, Main.parent,
+                        trn(
+                                "Overwrite ''{0}'' tag {1} from {2} with new value ''{3}''?",
+                                "Overwrite ''{0}'' tags {1} from {2} with new value ''{3}''?", existingValues.size(),
+                                key, Utils.joinAsHtmlUnorderedList(existingValues),
+                                DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(primitives, 10), newValue),
+                        tr("Overwrite key"),
+                        JOptionPane.YES_NO_OPTION,
+                        JOptionPane.QUESTION_MESSAGE,
+                        JOptionPane.YES_OPTION));
+        return Boolean.TRUE.equals(r);
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikiSearchTextResultListPanel.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikiSearchTextResultListPanel.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikiSearchTextResultListPanel.java	(revision 32887)
@@ -0,0 +1,24 @@
+package org.wikipedia.gui;
+
+import java.util.concurrent.Executors;
+
+import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel;
+import org.openstreetmap.josm.tools.Utils;
+import org.wikipedia.tools.Debouncer;
+
+abstract class WikiSearchTextResultListPanel<T> extends SearchTextResultListPanel<T> {
+
+    protected final Debouncer debouncer = new Debouncer(
+            Executors.newSingleThreadScheduledExecutor(Utils.newThreadFactory("wikipedia-search-%d", Thread.NORM_PRIORITY)));
+
+    public T getSelectedItem() {
+        final T selected = lsResult.getSelectedValue();
+        if (selected != null) {
+            return selected;
+        } else if (!lsResultModel.isEmpty()) {
+            return lsResultModel.getElementAt(0);
+        } else {
+            return null;
+        }
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikidataItemSearchDialog.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikidataItemSearchDialog.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikidataItemSearchDialog.java	(revision 32887)
@@ -0,0 +1,148 @@
+// License: GPL. For details, see LICENSE file.
+package org.wikipedia.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.JosmAction;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
+import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.tools.GBC;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.data.WikidataEntry;
+
+public final class WikidataItemSearchDialog extends ExtendedDialog {
+
+    private final Selector selector;
+    private final AutoCompletingComboBox targetKey;
+    private static final WikidataItemSearchDialog INSTANCE = new WikidataItemSearchDialog();
+
+    private WikidataItemSearchDialog() {
+        super(Main.parent, tr("Search Wikidata items"), tr("Add Tag"), tr("Cancel"));
+        this.selector = new Selector();
+        this.selector.setDblClickListener(e -> buttonAction(0, null));
+        this.targetKey = new AutoCompletingComboBox();
+        this.targetKey.setEditable(true);
+        this.targetKey.setSelectedItem(new AutoCompletionListItem("wikidata"));
+
+        final JPanel panel = new JPanel(new GridBagLayout());
+        panel.add(selector, GBC.eop().fill(GBC.BOTH));
+        panel.add(new JLabel(tr("Target key: ")));
+        panel.add(targetKey, GBC.eol().fill(GBC.HORIZONTAL));
+        setContent(panel, false);
+        setPreferredSize(new Dimension(600, 300));
+    }
+
+    /**
+     * Returns the unique instance of {@code MenuItemSearchDialog}.
+     *
+     * @return the unique instance of {@code MenuItemSearchDialog}.
+     */
+    public static synchronized WikidataItemSearchDialog getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public ExtendedDialog showDialog() {
+        initTargetKeys();
+        selector.init();
+        super.showDialog();
+        selector.clearSelection();
+        selector.requestFocus();
+        return this;
+    }
+
+    private void initTargetKeys() {
+        final DataSet editDataSet = Main.getLayerManager().getEditDataSet();
+        if (editDataSet == null) {
+            return;
+        }
+        final Collection<AutoCompletionListItem> keys = new TreeSet<>();
+        // from http://wiki.openstreetmap.org/wiki/Proposed_features/Wikidata#Tagging
+        keys.add(new AutoCompletionListItem("wikidata"));
+        keys.add(new AutoCompletionListItem("operator:wikidata"));
+        keys.add(new AutoCompletionListItem("brand:wikidata"));
+        keys.add(new AutoCompletionListItem("architect:wikidata"));
+        keys.add(new AutoCompletionListItem("artist:wikidata"));
+        keys.add(new AutoCompletionListItem("subject:wikidata"));
+        keys.add(new AutoCompletionListItem("name:etymology:wikidata"));
+        editDataSet.getAutoCompletionManager().getKeys().stream()
+                .filter(v -> v.getValue().contains("wikidata"))
+                .forEach(keys::add);
+        targetKey.setPossibleACItems(keys);
+    }
+
+    @Override
+    protected void buttonAction(int buttonIndex, ActionEvent evt) {
+        super.buttonAction(buttonIndex, evt);
+        if (buttonIndex != 0) {
+            return;
+        }
+        final WikidataEntry selected = selector.getSelectedItem();
+        if (selected == null) {
+            return;
+        }
+        final String key = Tag.removeWhiteSpaces(targetKey.getEditor().getItem().toString());
+        final String value = selected.createWikipediaTag().getValue();
+        WikipediaToggleDialog.AddWikipediaTagAction.addTag(new Tag(key, value));
+    }
+
+    private static class Selector extends WikiSearchTextResultListPanel<WikidataEntry> {
+
+        Selector() {
+            super();
+            lsResult.setCellRenderer(new DefaultListCellRenderer() {
+                @Override
+                public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                    final WikidataEntry entry = (WikidataEntry) value;
+                    final String labelText = "<html>" + entry.getLabelText();
+                    return super.getListCellRendererComponent(list, labelText, index, isSelected, cellHasFocus);
+                }
+            });
+        }
+
+        @Override
+        protected void filterItems() {
+            final String query = edSearchText.getText();
+            debouncer.debounce(getClass(), () -> {
+                final List<WikidataEntry> entries = query == null || query.isEmpty()
+                        ? Collections.emptyList()
+                        : WikipediaApp.getWikidataEntriesForQuery(WikipediaToggleDialog.wikipediaLang.get(), query, Locale.getDefault());
+                GuiHelper.runInEDT(() -> lsResultModel.setItems(entries));
+            }, 200, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    public static class Action extends JosmAction {
+
+        public Action() {
+            super(tr("Search Wikidata items"), "dialogs/wikidata", null,
+                    null, true, "dialogs/search-wikidata-items", false);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            WikidataItemSearchDialog.getInstance().showDialog();
+        }
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikidataTagCellRenderer.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikidataTagCellRenderer.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikidataTagCellRenderer.java	(revision 32887)
@@ -0,0 +1,84 @@
+package org.wikipedia.gui;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.tools.Utils;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.data.WikidataEntry;
+
+public class WikidataTagCellRenderer extends DefaultTableCellRenderer {
+
+    final Map<String, CompletableFuture<String>> labelCache = new ConcurrentHashMap<>();
+
+    @Override
+    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+        if (column != 1
+                || !(value instanceof Map<?, ?> && ((Map<?, ?>) value).size() == 1)) {
+            return null;
+        }
+        final String key = table.getValueAt(row, 0).toString();
+        if (!("wikidata".equals(key) || (key != null && key.endsWith(":wikidata")))) {
+            return null;
+        }
+
+        final String id = ((Map<?, ?>) value).keySet().iterator().next().toString();
+        final JLabel component = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+        if (WikipediaApp.WIKIDATA_PATTERN.matcher(id).matches()) {
+            return renderValues(Collections.singleton(id), table, component);
+        } else if (id.contains(";")) {
+            final List<String> ids = Arrays.asList(id.split("\\s*;\\s*"));
+            if (ids.stream().allMatch(i -> WikipediaApp.WIKIDATA_PATTERN.matcher(i).matches())) {
+                return renderValues(ids, table, component);
+            }
+        }
+        return null;
+    }
+
+    protected JLabel renderValues(Collection<String> ids, JTable table, JLabel component) {
+
+        ids.forEach(id ->
+                labelCache.computeIfAbsent(id, x ->
+                        CompletableFuture.supplyAsync(() -> WikipediaApp.getLabelForWikidata(x, Locale.getDefault())))
+        );
+
+        final Collection<String> texts = new ArrayList<>(ids.size());
+        for (String id : ids) {
+            if (!labelCache.get(id).isDone()) {
+                labelCache.get(id).thenRun(() -> GuiHelper.runInEDT(table::repaint));
+                return null;
+            }
+            final String label;
+            try {
+                label = labelCache.get(id).get();
+            } catch (InterruptedException | ExecutionException e) {
+                Main.warn("Could not fetch Wikidata label for " + id);
+                Main.warn(e);
+                return null;
+            }
+            if (label == null) {
+                return null;
+            }
+            texts.add(WikidataEntry.getLabelText(id, label));
+        }
+        component.setText("<html>" + texts.stream().collect(Collectors.joining("; ")));
+        component.setToolTipText("<html>" + Utils.joinAsHtmlUnorderedList(texts));
+        return component;
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikipediaCategorySearchDialog.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikipediaCategorySearchDialog.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikipediaCategorySearchDialog.java	(revision 32887)
@@ -0,0 +1,65 @@
+// License: GPL. For details, see LICENSE file.
+package org.wikipedia.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Dimension;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.wikipedia.WikipediaApp;
+
+final class WikipediaCategorySearchDialog extends ExtendedDialog {
+
+    private final Selector selector;
+    private static final WikipediaCategorySearchDialog INSTANCE = new WikipediaCategorySearchDialog();
+
+    private WikipediaCategorySearchDialog() {
+        super(Main.parent, tr("Search Wikipedia category"), new String[]{tr("Load category"), tr("Cancel")});
+        this.selector = new Selector();
+        this.selector.setDblClickListener(e -> buttonAction(0, null));
+
+        setContent(selector, false);
+        setPreferredSize(new Dimension(600, 300));
+    }
+
+    /**
+     * Returns the unique instance of {@code MenuItemSearchDialog}.
+     *
+     * @return the unique instance of {@code MenuItemSearchDialog}.
+     */
+    public static synchronized WikipediaCategorySearchDialog getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public ExtendedDialog showDialog() {
+        selector.init();
+        super.showDialog();
+        selector.clearSelection();
+        selector.requestFocus();
+        return this;
+    }
+
+    String getCategory() {
+        return selector.getSelectedItem();
+    }
+
+    private static class Selector extends WikiSearchTextResultListPanel<String> {
+
+        @Override
+        protected void filterItems() {
+            final String query = edSearchText.getText();
+            debouncer.debounce(getClass(), () -> {
+                final List<String> entries = query == null || query.isEmpty()
+                        ? Collections.emptyList()
+                        : WikipediaApp.getCategoriesForPrefix(WikipediaToggleDialog.wikipediaLang.get(), query);
+                GuiHelper.runInEDT(() -> lsResultModel.setItems(entries));
+            }, 200, TimeUnit.MILLISECONDS);
+        }
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikipediaToggleDialog.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikipediaToggleDialog.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/gui/WikipediaToggleDialog.java	(revision 32887)
@@ -0,0 +1,389 @@
+// License: GPL. See LICENSE file for details.
+package org.wikipedia.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingWorker;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.search.SearchAction;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Tag;
+import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
+import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
+import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+import org.openstreetmap.josm.data.preferences.StringProperty;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
+import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.LanguageInfo;
+import org.openstreetmap.josm.tools.OpenBrowser;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.actions.FetchWikidataAction;
+import org.wikipedia.data.WikipediaEntry;
+
+public class WikipediaToggleDialog extends ToggleDialog implements ActiveLayerChangeListener, DataSetListenerAdapter.Listener {
+
+    public WikipediaToggleDialog() {
+        super(tr("Wikipedia"), "wikipedia", tr("Fetch Wikipedia articles with coordinates"), null, 150);
+        createLayout(list, true, Arrays.asList(
+                new SideButton(new WikipediaLoadCoordinatesAction(false)),
+                new SideButton(new WikipediaLoadCoordinatesAction(true)),
+                new SideButton(new WikipediaLoadCategoryAction()),
+                new SideButton(new PasteWikipediaArticlesAction()),
+                new SideButton(new AddWikipediaTagAction(list)),
+                new SideButton(new WikipediaSettingsAction(), false)));
+        updateTitle();
+    }
+    /** A string describing the context (use-case) for determining the dialog title */
+    String titleContext = null;
+    static final StringProperty wikipediaLang = new StringProperty("wikipedia.lang", LanguageInfo.getJOSMLocaleCode().substring(0, 2));
+    final Set<String> articles = new HashSet<>();
+    final DefaultListModel<WikipediaEntry> model = new DefaultListModel<>();
+    final JList<WikipediaEntry> list = new JList<WikipediaEntry>(model) {
+
+        {
+            setToolTipText(tr("Double click on item to search for object with article name (and center coordinate)"));
+            addMouseListener(new MouseAdapter() {
+
+                @Override
+                public void mouseClicked(MouseEvent e) {
+                    if (e.getClickCount() == 2 && getSelectedValue() != null) {
+                        final WikipediaEntry entry = getSelectedValue();
+                        if (entry.coordinate != null) {
+                            BoundingXYVisitor bbox = new BoundingXYVisitor();
+                            bbox.visit(entry.coordinate);
+                            Main.map.mapView.zoomTo(bbox);
+                        }
+                        final String search = Optional.ofNullable(entry.label).orElse(entry.wikipediaArticle).replaceAll("\\(.*\\)", "");
+                        SearchAction.search(search, SearchAction.SearchMode.replace);
+                    }
+                }
+            });
+
+            setCellRenderer(new DefaultListCellRenderer() {
+
+                @Override
+                public JLabel getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                    final WikipediaEntry entry = (WikipediaEntry) value;
+                    final String labelText = "<html>" + entry.getLabelText();
+                    final JLabel label = (JLabel) super.getListCellRendererComponent(list, labelText, index, isSelected, cellHasFocus);
+                    if (entry.getWiwosmStatus() != null && entry.getWiwosmStatus()) {
+                        label.setIcon(ImageProvider.getIfAvailable("misc", "grey_check"));
+                        label.setToolTipText(/* I18n: WIWOSM server already links Wikipedia article to object/s */ tr("Available via WIWOSM server"));
+                    } else if (articles.contains(entry.wikipediaArticle)) {
+                        label.setIcon(ImageProvider.getIfAvailable("misc", "green_check"));
+                        label.setToolTipText(/* I18n: object/s from dataset contain link to Wikipedia article */ tr("Available in local dataset"));
+                    } else {
+                        label.setToolTipText(tr("Not linked yet"));
+                    }
+                    return label;
+                }
+            });
+
+            final JPopupMenu popupMenu = new JPopupMenu();
+            popupMenu.add(new OpenWikipediaArticleAction());
+            popupMenu.add(new ZoomToWikipediaArticleAction());
+            setComponentPopupMenu(popupMenu);
+        }
+    };
+
+    private void updateTitle() {
+        final String lang = getLanguageOfFirstItem();
+        final String host = WikipediaApp.getSiteUrl(lang).split("/+")[1];
+        if (titleContext == null) {
+            setTitle(host);
+        } else {
+            setTitle(tr("{0}: {1}", host, titleContext));
+        }
+    }
+
+    private String getLanguageOfFirstItem() {
+        try {
+            return list.getModel().getElementAt(0).wikipediaLang;
+        } catch (ArrayIndexOutOfBoundsException ignore) {
+            return wikipediaLang.get();
+        }
+    }
+
+    class WikipediaLoadCoordinatesAction extends AbstractAction {
+
+        private final boolean wikidata;
+
+        public WikipediaLoadCoordinatesAction(boolean wikidata) {
+            super(wikidata ? tr("Wikidata") : tr("Coordinates"));
+            this.wikidata = wikidata;
+            new ImageProvider("dialogs", wikidata ? "wikidata" : "wikipedia").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, wikidata
+                    ? tr("Fetches all coordinates from Wikidata in the current view")
+                    : tr("Fetches all coordinates from Wikipedia in the current view"));
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            try {
+                // determine bbox
+                final LatLon min = Main.map.mapView.getLatLon(0, Main.map.mapView.getHeight());
+                final LatLon max = Main.map.mapView.getLatLon(Main.map.mapView.getWidth(), 0);
+                // add entries to list model
+                titleContext = tr("coordinates");
+                updateTitle();
+                new UpdateWikipediaArticlesSwingWorker() {
+
+                    @Override
+                    List<WikipediaEntry> getEntries() {
+                        return WikipediaApp.getEntriesFromCoordinates(
+                                wikidata ? "wikidata" : wikipediaLang.get(), min, max);
+                    }
+                }.execute();
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    abstract class UpdateWikipediaArticlesSwingWorker extends SwingWorker<Void, WikipediaEntry> {
+
+        abstract List<WikipediaEntry> getEntries();
+
+        @Override
+        protected Void doInBackground() throws Exception {
+            final List<WikipediaEntry> entries = getEntries();
+            entries.sort(null);
+            publish(entries.toArray(new WikipediaEntry[entries.size()]));
+            WikipediaApp.partitionList(entries, 20).forEach(chunk -> {
+                WikipediaApp.updateWIWOSMStatus(chunk.get(0).wikipediaLang, chunk);
+                list.repaint();
+            });
+            return null;
+        }
+
+        @Override
+        protected void process(List<WikipediaEntry> chunks) {
+            model.clear();
+            chunks.forEach(model::addElement);
+            updateTitle();
+            updateWikipediaArticles();
+        }
+
+    }
+
+    class WikipediaLoadCategoryAction extends AbstractAction {
+
+        public WikipediaLoadCategoryAction() {
+            super(tr("Category"));
+            new ImageProvider("data", "sequence").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, tr("Fetches a list of all Wikipedia articles of a category"));
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            final WikipediaCategorySearchDialog categorySearchDialog = WikipediaCategorySearchDialog.getInstance();
+            categorySearchDialog.showDialog();
+            if (categorySearchDialog.getValue() != 1) {
+                return;
+            }
+            final String category = categorySearchDialog.getCategory();
+            if (category == null) {
+                return;
+            }
+
+            titleContext = category;
+            updateTitle();
+
+            new UpdateWikipediaArticlesSwingWorker() {
+                @Override
+                List<WikipediaEntry> getEntries() {
+                    return WikipediaApp.getEntriesFromCategory(
+                            wikipediaLang.get(), category, Main.pref.getInteger("wikipedia.depth", 3));
+                }
+            }.execute();
+        }
+    }
+
+    class PasteWikipediaArticlesAction extends AbstractAction {
+
+        public PasteWikipediaArticlesAction() {
+            super(tr("Clipboard"));
+            new ImageProvider("paste").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, tr("Pastes Wikipedia articles from the system clipboard"));
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            titleContext = tr("clipboard");
+            updateTitle();
+            new UpdateWikipediaArticlesSwingWorker() {
+
+                @Override
+                List<WikipediaEntry> getEntries() {
+                    return WikipediaApp.getEntriesFromClipboard(wikipediaLang.get());
+                }
+            }.execute();
+        }
+    }
+
+    class OpenWikipediaArticleAction extends AbstractAction {
+
+        public OpenWikipediaArticleAction() {
+            super(tr("Open Article"));
+            new ImageProvider("browser").getResource().attachImageIcon(this);
+            putValue(SHORT_DESCRIPTION, tr("Opens the Wikipedia article of the selected item in a browser"));
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            if (list.getSelectedValue() != null) {
+                final String url = list.getSelectedValue().getBrowserUrl();
+                Main.info("Wikipedia: opening " + url);
+                OpenBrowser.displayUrl(url);
+            }
+        }
+    }
+
+    class WikipediaSettingsAction extends AbstractAction {
+
+        public WikipediaSettingsAction() {
+            super(tr("Language"));
+            new ImageProvider("dialogs/settings").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, tr("Sets the default language for the Wikipedia articles"));
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            String lang = JOptionPane.showInputDialog(
+                    Main.parent,
+                    tr("Enter the Wikipedia language"),
+                    wikipediaLang.get());
+            if (lang != null) {
+                wikipediaLang.put(lang);
+                updateTitle();
+                updateWikipediaArticles();
+            }
+        }
+    }
+
+    static class AddWikipediaTagAction extends AbstractAction {
+
+        private final JList<WikipediaEntry> list;
+
+        public AddWikipediaTagAction(JList<WikipediaEntry> list) {
+            super(tr("Add Tag"));
+            this.list = list;
+            new ImageProvider("pastetags").getResource().attachImageIcon(this, true);
+            putValue(SHORT_DESCRIPTION, tr("Adds a ''wikipedia'' tag corresponding to this article to the selected objects"));
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            addTag(list.getSelectedValue());
+        }
+
+        static void addTag(WikipediaEntry entry) {
+            if (entry == null) {
+                return;
+            }
+            addTag(entry.createWikipediaTag());
+        }
+
+        static void addTag(Tag tag) {
+            if (tag == null) {
+                return;
+            }
+            final Collection<OsmPrimitive> selected = Main.getLayerManager().getEditDataSet().getSelected();
+            if (!GuiUtils.confirmOverwrite(tag.getKey(), tag.getValue(), selected)) {
+                return;
+            }
+            ChangePropertyCommand cmd = new ChangePropertyCommand(
+                    selected,
+                    tag.getKey(), tag.getValue());
+            Main.main.undoRedo.add(cmd);
+            Main.worker.submit(new FetchWikidataAction.Fetcher(selected));
+        }
+    }
+
+    class ZoomToWikipediaArticleAction extends AbstractAction {
+
+        ZoomToWikipediaArticleAction() {
+            super(tr("Zoom to selection"));
+            new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this);
+            putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            final WikipediaEntry entry = list.getSelectedValue();
+            if (entry == null) {
+                return;
+            }
+            final LatLon latLon = entry.coordinate != null
+                    ? entry.coordinate
+                    : WikipediaApp.getCoordinateForArticle(entry.wikipediaLang, entry.wikipediaArticle);
+            if (latLon == null) {
+                return;
+            }
+            Main.map.mapView.zoomTo(latLon);
+        }
+    }
+
+    protected void updateWikipediaArticles() {
+        final String language = getLanguageOfFirstItem();
+        articles.clear();
+        if (Main.main != null && Main.getLayerManager().getEditDataSet() != null) {
+            Main.getLayerManager().getEditDataSet().allPrimitives().stream()
+                    .flatMap(p -> WikipediaApp.getWikipediaArticles(language, p))
+                    .forEach(articles::add);
+        }
+    }
+
+    private final DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this);
+
+    @Override
+    public void showNotify() {
+        DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED);
+        Main.getLayerManager().addActiveLayerChangeListener(this);
+        updateWikipediaArticles();
+    }
+
+    @Override
+    public void hideNotify() {
+        DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter);
+        Main.getLayerManager().removeActiveLayerChangeListener(this);
+        articles.clear();
+    }
+
+    @Override
+    public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
+        updateWikipediaArticles();
+        list.repaint();
+    }
+
+    @Override
+    public void processDatasetEvent(AbstractDatasetChangedEvent event) {
+        updateWikipediaArticles();
+        list.repaint();
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/tools/Debouncer.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/tools/Debouncer.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/tools/Debouncer.java	(revision 32887)
@@ -0,0 +1,36 @@
+package org.wikipedia.tools;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class Debouncer {
+    private final ScheduledExecutorService scheduler;
+    private final ConcurrentHashMap<Object, Future<?>> delayedMap = new ConcurrentHashMap<>();
+
+    public Debouncer(ScheduledExecutorService scheduler) {
+        this.scheduler = scheduler;
+    }
+
+    /**
+     * Debounces {@code callable} by {@code delay}, i.e., schedules it to be executed after {@code delay},
+     * or cancels its execution if the method is called with the same key within the {@code delay} again.
+     */
+    public void debounce(final Object key, final Runnable runnable, long delay, TimeUnit unit) {
+        final Future<?> prev = delayedMap.put(key, scheduler.schedule(() -> {
+            try {
+                runnable.run();
+            } finally {
+                delayedMap.remove(key);
+            }
+        }, delay, unit));
+        if (prev != null) {
+            prev.cancel(true);
+        }
+    }
+
+    public void shutdown() {
+        scheduler.shutdownNow();
+    }
+}
Index: /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/tools/XPath.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/tools/XPath.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/src/org/wikipedia/tools/XPath.java	(revision 32887)
@@ -0,0 +1,139 @@
+// License: GPL. See LICENSE file for details./*
+package org.wikipedia.tools;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathFunctionResolver;
+import javax.xml.xpath.XPathVariableResolver;
+import java.util.AbstractList;
+import java.util.Collection;
+
+public class XPath implements javax.xml.xpath.XPath {
+    private final javax.xml.xpath.XPath xPath;
+    private static final XPath INSTANCE = new XPath(XPathFactory.newInstance().newXPath());
+
+    private XPath(javax.xml.xpath.XPath xPath) {
+        this.xPath = xPath;
+    }
+
+    public static XPath getInstance() {
+        return INSTANCE;
+    }
+
+    public static class UncheckedXPathExpressionException extends RuntimeException {
+        public UncheckedXPathExpressionException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+    public String evaluateString(String expression, Object item) throws UncheckedXPathExpressionException {
+        try {
+            return (String) xPath.evaluate(expression, item, XPathConstants.STRING);
+        } catch (XPathExpressionException e) {
+            throw new UncheckedXPathExpressionException(e);
+        }
+    }
+
+    public double evaluateDouble(String expression, Object item) throws UncheckedXPathExpressionException {
+        try {
+            return ((Number) xPath.evaluate(expression, item, XPathConstants.NUMBER)).doubleValue();
+        } catch (XPathExpressionException e) {
+            throw new UncheckedXPathExpressionException(e);
+        }
+    }
+
+    public Node evaluateNode(String expression, Object item) throws UncheckedXPathExpressionException {
+        try {
+            return (Node) evaluate(expression, item, XPathConstants.NODE);
+        } catch (XPathExpressionException e) {
+            throw new UncheckedXPathExpressionException(e);
+        }
+    }
+
+    public Collection<Node> evaluateNodes(String expression, Object item) throws UncheckedXPathExpressionException {
+        try {
+            final NodeList nodes = (NodeList) evaluate(expression, item, XPathConstants.NODESET);
+            return new AbstractList<Node>() {
+                @Override
+                public Node get(int index) {
+                    return nodes.item(index);
+                }
+
+                @Override
+                public int size() {
+                    return nodes.getLength();
+                }
+            };
+        } catch (XPathExpressionException e) {
+            throw new UncheckedXPathExpressionException(e);
+        }
+    }
+
+    @Override
+    public void reset() {
+        xPath.reset();
+    }
+
+    @Override
+    public void setXPathVariableResolver(XPathVariableResolver resolver) {
+        xPath.setXPathVariableResolver(resolver);
+    }
+
+    @Override
+    public XPathVariableResolver getXPathVariableResolver() {
+        return xPath.getXPathVariableResolver();
+    }
+
+    @Override
+    public void setXPathFunctionResolver(XPathFunctionResolver resolver) {
+        xPath.setXPathFunctionResolver(resolver);
+    }
+
+    @Override
+    public XPathFunctionResolver getXPathFunctionResolver() {
+        return xPath.getXPathFunctionResolver();
+    }
+
+    @Override
+    public void setNamespaceContext(NamespaceContext nsContext) {
+        xPath.setNamespaceContext(nsContext);
+    }
+
+    @Override
+    public NamespaceContext getNamespaceContext() {
+        return xPath.getNamespaceContext();
+    }
+
+    @Override
+    public XPathExpression compile(String expression) throws XPathExpressionException {
+        return xPath.compile(expression);
+    }
+
+    @Override
+    public Object evaluate(String expression, InputSource source, QName returnType) throws XPathExpressionException {
+        return xPath.evaluate(expression, source, returnType);
+    }
+
+    @Override
+    public String evaluate(String expression, InputSource source) throws XPathExpressionException {
+        return xPath.evaluate(expression, source);
+    }
+
+    @Override
+    public Object evaluate(String expression, Object item, QName returnType) throws XPathExpressionException {
+        return xPath.evaluate(expression, item, returnType);
+    }
+
+    @Override
+    public String evaluate(String expression, Object item) throws XPathExpressionException {
+        return xPath.evaluate(expression, item);
+    }
+}
Index: plications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/WikidataTagCellRendererTest.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/WikidataTagCellRendererTest.java	(revision 32886)
+++ 	(revision )
@@ -1,45 +1,0 @@
-package org.wikipedia;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.swing.JLabel;
-import javax.swing.JTable;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
-
-public class WikidataTagCellRendererTest {
-
-    /**
-     * Setup test.
-     */
-    @Rule
-    public JOSMTestRules rules = new JOSMTestRules().preferences();
-
-    @Test
-    public void testRenderLabel() throws Exception {
-        final List<String> ids = Arrays.asList("Q84", "Q1741", "Q278250");
-        final WikidataTagCellRenderer renderer = new WikidataTagCellRenderer();
-        renderer.renderValues(ids, new JTable(), new JLabel());
-        for (String id : ids) {
-            // wait for labels to be fetched
-            renderer.labelCache.get(id).get();
-        }
-        final JLabel label = renderer.renderValues(ids, new JTable(), new JLabel());
-        assertNotNull(label);
-        assertThat(label.getText(), is("<html>" +
-                "Q84 <span color='gray'>London</span>; " +
-                "Q1741 <span color='gray'>Vienna</span>; " +
-                "Q278250 <span color='gray'>Völs</span>"));
-        assertThat(label.getToolTipText(), is("<html><ul>" +
-                "<li>Q84 <span color='gray'>London</span></li>" +
-                "<li>Q1741 <span color='gray'>Vienna</span></li>" +
-                "<li>Q278250 <span color='gray'>Völs</span></li></ul>"));
-    }
-}
Index: /applications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/WikipediaAppTest.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/WikipediaAppTest.java	(revision 32886)
+++ /applications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/WikipediaAppTest.java	(revision 32887)
@@ -1,12 +1,11 @@
 package org.wikipedia;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
-import org.wikipedia.WikipediaApp.WikipediaEntry;
-import org.wikipedia.WikipediaApp.WikipediaLangArticle;
+import org.wikipedia.data.WikidataEntry;
+import org.wikipedia.data.WikipediaEntry;
+import org.wikipedia.data.WikipediaLangArticle;
 
 import java.util.Arrays;
@@ -152,6 +151,6 @@
     @Test
     public void testForQuery() throws Exception {
-        final List<WikipediaApp.WikidataEntry> de = WikipediaApp.getWikidataEntriesForQuery("de", "Österreich", Locale.GERMAN);
-        final List<WikipediaApp.WikidataEntry> en = WikipediaApp.getWikidataEntriesForQuery("de", "Österreich", Locale.ENGLISH);
+        final List<WikidataEntry> de = WikipediaApp.getWikidataEntriesForQuery("de", "Österreich", Locale.GERMAN);
+        final List<WikidataEntry> en = WikipediaApp.getWikidataEntriesForQuery("de", "Österreich", Locale.ENGLISH);
         assertThat(de.get(0).wikipediaArticle, is("Q40"));
         assertThat(de.get(0).wikipediaLang, is("wikidata"));
@@ -191,7 +190,7 @@
         // not found -> null
         assertThat(WikipediaApp.getLabelForWikidata("Q" + Long.MAX_VALUE, Locale.ENGLISH), nullValue());
-        final WikipediaApp.WikidataEntry q84 = new WikipediaApp.WikidataEntry("Q84", null, null, null);
-        final WikipediaApp.WikidataEntry q1741 = new WikipediaApp.WikidataEntry("Q1741", null, null, null);
-        final List<WikipediaApp.WikidataEntry> twoLabels = WikipediaApp.getLabelForWikidata(Arrays.asList(q84, q1741), Locale.GERMAN);
+        final WikidataEntry q84 = new WikidataEntry("Q84", null, null, null);
+        final WikidataEntry q1741 = new WikidataEntry("Q1741", null, null, null);
+        final List<WikidataEntry> twoLabels = WikipediaApp.getLabelForWikidata(Arrays.asList(q84, q1741), Locale.GERMAN);
         assertThat(twoLabels.get(0).label, is("London"));
         assertThat(twoLabels.get(1).label, is("Wien"));
Index: /applications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/gui/WikidataTagCellRendererTest.java
===================================================================
--- /applications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/gui/WikidataTagCellRendererTest.java	(revision 32887)
+++ /applications/editors/josm/plugins/wikipedia/test/unit/org/wikipedia/gui/WikidataTagCellRendererTest.java	(revision 32887)
@@ -0,0 +1,46 @@
+package org.wikipedia.gui;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.wikipedia.gui.WikidataTagCellRenderer;
+
+public class WikidataTagCellRendererTest {
+
+    /**
+     * Setup test.
+     */
+    @Rule
+    public JOSMTestRules rules = new JOSMTestRules().preferences();
+
+    @Test
+    public void testRenderLabel() throws Exception {
+        final List<String> ids = Arrays.asList("Q84", "Q1741", "Q278250");
+        final WikidataTagCellRenderer renderer = new WikidataTagCellRenderer();
+        renderer.renderValues(ids, new JTable(), new JLabel());
+        for (String id : ids) {
+            // wait for labels to be fetched
+            renderer.labelCache.get(id).get();
+        }
+        final JLabel label = renderer.renderValues(ids, new JTable(), new JLabel());
+        assertNotNull(label);
+        assertThat(label.getText(), is("<html>" +
+                "Q84 <span color='gray'>London</span>; " +
+                "Q1741 <span color='gray'>Vienna</span>; " +
+                "Q278250 <span color='gray'>Völs</span>"));
+        assertThat(label.getToolTipText(), is("<html><ul>" +
+                "<li>Q84 <span color='gray'>London</span></li>" +
+                "<li>Q1741 <span color='gray'>Vienna</span></li>" +
+                "<li>Q278250 <span color='gray'>Völs</span></li></ul>"));
+    }
+}
