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}