001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.io.IOException;
005import java.io.InputStream;
006import java.net.URL;
007import java.util.List;
008import java.util.Optional;
009import java.util.stream.Collectors;
010
011import javax.xml.parsers.ParserConfigurationException;
012import javax.xml.xpath.XPath;
013import javax.xml.xpath.XPathConstants;
014import javax.xml.xpath.XPathExpressionException;
015import javax.xml.xpath.XPathFactory;
016
017import org.w3c.dom.Document;
018import org.w3c.dom.Node;
019import org.xml.sax.SAXException;
020
021/**
022 * Interaction with Mediawiki instances, such as the OSM wiki.
023 * @since 14641
024 */
025public class Mediawiki {
026
027    private final String baseUrl;
028
029    /**
030     * Constructs a new {@code Mediawiki} for the given base URL.
031     * @param baseUrl The wiki base URL
032     */
033    public Mediawiki(String baseUrl) {
034        this.baseUrl = baseUrl;
035    }
036
037    /**
038     * Determines which page exists on the Mediawiki instance.
039     * @param pages the pages to check
040     * @return the first existing page
041     * @throws IOException if any I/O error occurs
042     * @throws ParserConfigurationException if a parser cannot be created
043     * @throws SAXException if any XML error occurs
044     * @throws XPathExpressionException if any error in an XPath expression occurs
045     */
046    public Optional<String> findExistingPage(List<String> pages)
047            throws IOException, ParserConfigurationException, SAXException, XPathExpressionException {
048        // find a page that actually exists in the wiki
049        // API documentation: https://wiki.openstreetmap.org/w/api.php?action=help&modules=query
050        final URL url = new URL(baseUrl + "/w/api.php?action=query&format=xml&titles=" + pages.stream()
051                .map(Utils::encodeUrl)
052                .collect(Collectors.joining("|"))
053        );
054        final HttpClient.Response conn = HttpClient.create(url).connect();
055        final Document document;
056        try (InputStream content = conn.getContent()) {
057            document = XmlUtils.parseSafeDOM(content);
058        }
059        conn.disconnect();
060        final XPath xPath = XPathFactory.newInstance().newXPath();
061        for (String page : pages) {
062            String normalized = xPath.evaluate("/api/query/normalized/n[@from='" + page + "']/@to", document);
063            if (normalized == null || normalized.isEmpty()) {
064                normalized = page;
065            }
066            final Node node = (Node) xPath.evaluate("/api/query/pages/page[@title='" + normalized + "']", document, XPathConstants.NODE);
067            if (node != null
068                    && node.getAttributes().getNamedItem("missing") == null
069                    && node.getAttributes().getNamedItem("invalid") == null) {
070                return Optional.of(page);
071            }
072        }
073        return Optional.empty();
074    }
075}