001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.io.InputStream; 008import java.io.InputStreamReader; 009import java.nio.charset.StandardCharsets; 010 011import javax.xml.parsers.ParserConfigurationException; 012import javax.xml.parsers.SAXParserFactory; 013 014import org.openstreetmap.josm.Main; 015import org.openstreetmap.josm.data.osm.history.HistoryDataSet; 016import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 017import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 018import org.openstreetmap.josm.gui.progress.ProgressMonitor; 019import org.openstreetmap.josm.tools.CheckParameterUtil; 020import org.xml.sax.Attributes; 021import org.xml.sax.InputSource; 022import org.xml.sax.SAXException; 023 024/** 025 * Parser for OSM history data. 026 * 027 * It is slightly different from {@link OsmReader} because we don't build an internal graph of 028 * {@link org.openstreetmap.josm.data.osm.OsmPrimitive}s. We use objects derived from 029 * {@link HistoryOsmPrimitive} instead and we keep the data in a dedicated {@link HistoryDataSet}. 030 * @since 1670 031 */ 032public class OsmHistoryReader { 033 034 private final InputStream in; 035 private final HistoryDataSet data; 036 037 private class Parser extends AbstractParser { 038 039 protected String getCurrentPosition() { 040 if (locator == null) 041 return ""; 042 return "(" + locator.getLineNumber() + "," + locator.getColumnNumber() + ")"; 043 } 044 045 @Override 046 protected void throwException(String message) throws SAXException { 047 throw new SAXException(getCurrentPosition() + message); 048 } 049 050 @Override 051 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 052 doStartElement(qName, atts); 053 } 054 055 @Override 056 public void endElement(String uri, String localName, String qName) throws SAXException { 057 if ("node".equals(qName) 058 || "way".equals(qName) 059 || "relation".equals(qName)) { 060 data.put(currentPrimitive); 061 } 062 } 063 } 064 065 /** 066 * Constructs a new {@code OsmHistoryReader}. 067 * 068 * @param source the input stream with the history content as XML document. Must not be null. 069 * @throws IllegalArgumentException if source is {@code null}. 070 */ 071 public OsmHistoryReader(InputStream source) { 072 CheckParameterUtil.ensureParameterNotNull(source, "source"); 073 this.in = source; 074 this.data = new HistoryDataSet(); 075 } 076 077 /** 078 * Parses the content. 079 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null 080 * @return the parsed data 081 * @throws SAXException If any SAX errors occur during processing. 082 * @throws IOException If any IO errors occur. 083 */ 084 public HistoryDataSet parse(ProgressMonitor progressMonitor) throws SAXException, IOException { 085 InputSource inputSource = new InputSource(new InputStreamReader(in, StandardCharsets.UTF_8)); 086 progressMonitor.beginTask(tr("Parsing OSM history data ...")); 087 try { 088 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, new Parser()); 089 } catch (ParserConfigurationException e) { 090 Main.error(e); // broken SAXException chaining 091 throw new SAXException(e); 092 } finally { 093 progressMonitor.finishTask(); 094 } 095 return data; 096 } 097}