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