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; 012 013import org.openstreetmap.josm.Main; 014import org.openstreetmap.josm.data.osm.history.HistoryDataSet; 015import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 016import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 017import org.openstreetmap.josm.gui.progress.ProgressMonitor; 018import org.openstreetmap.josm.tools.CheckParameterUtil; 019import org.openstreetmap.josm.tools.Utils; 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 protected void throwException(String message, Exception e) throws SAXException { 052 throw new SAXException(getCurrentPosition() + message, e); 053 } 054 055 @Override 056 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 057 doStartElement(qName, atts); 058 } 059 060 @Override 061 public void endElement(String uri, String localName, String qName) throws SAXException { 062 if ("node".equals(qName) 063 || "way".equals(qName) 064 || "relation".equals(qName)) { 065 data.put(currentPrimitive); 066 } 067 } 068 } 069 070 /** 071 * Constructs a new {@code OsmHistoryReader}. 072 * 073 * @param source the input stream with the history content as XML document. Must not be null. 074 * @throws IllegalArgumentException if source is {@code null}. 075 */ 076 public OsmHistoryReader(InputStream source) { 077 CheckParameterUtil.ensureParameterNotNull(source, "source"); 078 this.in = source; 079 this.data = new HistoryDataSet(); 080 } 081 082 /** 083 * Parses the content. 084 * @param progressMonitor the progress monitor. Set to {@link NullProgressMonitor#INSTANCE} if null 085 * @return the parsed data 086 * @throws SAXException If any SAX errors occur during processing. 087 * @throws IOException If any IO errors occur. 088 */ 089 public HistoryDataSet parse(ProgressMonitor progressMonitor) throws SAXException, IOException { 090 InputSource inputSource = new InputSource(new InputStreamReader(in, StandardCharsets.UTF_8)); 091 progressMonitor.beginTask(tr("Parsing OSM history data ...")); 092 try { 093 Utils.parseSafeSAX(inputSource, new Parser()); 094 } catch (ParserConfigurationException e) { 095 Main.error(e); // broken SAXException chaining 096 throw new SAXException(e); 097 } finally { 098 progressMonitor.finishTask(); 099 } 100 return data; 101 } 102}