package nl.wldelft.timeseriesparsers; package nl.wldelft.timeseriesparsers; import nl.wldelft.util.TextUtils; import nl.wldelft.util.TimeZoneUtils; import nl.wldelft.util.io.XmlParser; import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader; import nl.wldelft.util.timeseries.TimeSeriesContentHandler; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.util.LinkedHashMap; import java.util.Map; /** * * * @autor pelgrim * @author ekkelenk * Date: 8/9/13 * Time: 9:35 AM * Import of content reviewer. */ public class AifsMLTimeSeriesParser implements XmlParser<TimeSeriesContentHandler> { private TimeSeriesContentHandler contentHandler = null; private XMLStreamReader reader = null; private DefaultTimeSeriesHeader header = null; //Use for all private String title = null; private String identifier = null; private String sequenceNumber = null; @Override public void parse(XMLStreamReader reader, String virtualFileName, TimeSeriesContentHandler contentHandler) throws Exception { this.reader = reader; this.contentHandler = contentHandler; header = new DefaultTimeSeriesHeader(); reader.require(XMLStreamConstants.START_DOCUMENT, null, null); reader.nextTag(); reader.require(XMLStreamConstants.START_ELEMENT, null, "product"); reader.nextTag(); //noinspection SpellCheckingInspection reader.require(XMLStreamConstants.START_ELEMENT, null, "amoc"); reader.nextTag(); reader.require(XMLStreamConstants.START_ELEMENT, null, "source"); parseIdentifier(); reader.require(XMLStreamConstants.START_ELEMENT, null, "warning"); reader.nextTag(); reader.require(XMLStreamConstants.START_ELEMENT, null, "warning-info"); parseWarningInfo(); parseAreas(); } /** * filter the warning sequence and warning title text attributes from the text elements. * they will be used for all areas. * * @throws XMLStreamException */ private void parseWarningInfo() throws XMLStreamException { do { if (!goTo("text")) { // no more text areas, return. return; } // we only need the sequence number and the title. if (this.sequenceNumber == null || this.title == null) { String type = reader.getAttributeValue(null, "type"); if (TextUtils.equals("warning_sequence", type)) { this.sequenceNumber = reader.getElementText(); } else if (TextUtils.equals("warning_title_text", type)) { this.title = reader.getElementText(); } } if (this.sequenceNumber != null && this.title != null) { // we're finished with the text elements, skip to the areas goTo("area"); return; } } while (reader.hasNext()); } /** * Retrieve the identifier from the source element * * @throws javax.xml.stream.XMLStreamException; */ private void parseIdentifier() throws XMLStreamException { goTo("identifier"); if (TextUtils.equals(reader.getLocalName(), "identifier")) { this.identifier = reader.getElementText(); } // only identifier inside the source tag. Jump to warning. goTo("warning"); } /** * Retrieve the locationId from the forecast element(s) and call separate method to parse forecast data * * @throws javax.xml.stream.XMLStreamException; */ private void parseAreas() throws XMLStreamException { do { if (!goTo("area")) { return; } String locationId = reader.getAttributeValue(null, "aac"); parseForecastData(locationId); } while (reader.hasNext()); } /** * helper class to store forecast data. */ private static class ForecastData { private String parameter = null; private String value = null; private String unit = null; private String localTime = null; private String comment = null; } /** * Retrieve the variable, value, unit, time and comment from the forecast data element(s) * Retrieve comment text from forecast text element * * @throws javax.xml.stream.XMLStreamException; */ private void parseForecastData(String locationId) throws XMLStreamException { // not every area has a forecast-period. if (!goTo("forecast-period")) { return; } // get the start-time-utc String externalForecastTime = reader.getAttributeValue(null, "start-time-utc"); // the sequences are split over different elements. We have to merge them. We'll use a sequence map with String[]. Map<String, ForecastData> forecastSequenceMap = new LinkedHashMap(); do { if (!goTo("element")) break; String sequence = reader.getAttributeValue(null, "sequence"); ForecastData forecastData = forecastSequenceMap.get(sequence); if (forecastData == null) { forecastData = new ForecastData(); forecastSequenceMap.put(sequence, forecastData); } String type = reader.getAttributeValue(null, "type"); String units = reader.getAttributeValue(null, "units"); // 0 = parameter, 1 = value 2 = unit 3 = local time 4 comment if (TextUtils.equals("prediction", type)) { // time-local will be set from the prediction element. See comment in [FEWS-11311] String timeLocal = reader.getAttributeValue(null, "time-local"); String value = reader.getElementText(); forecastData.parameter = "prediction"; forecastData.localTime = timeLocal; forecastData.comment = value; } else if (TextUtils.equals("value", type)) { forecastData.value = reader.getElementText(); forecastData.unit = units; } } while (reader.hasNext()); //noinspection SpellCheckingInspection header.setForecastTime(TimeZoneUtils.GMT, "yyyy-MM-dd'T'HH:mm:ss'Z'", externalForecastTime); header.setLocationId(locationId); // The forecast sequence map contains a string array with the following elements: // 0 = parameter, 1 = value 2 = unit 3 = local time 4 comment for (ForecastData forecastData : forecastSequenceMap.values()) { contentHandler.setComment(identifier + " #" + sequenceNumber + ": " + title + '\n' + forecastData.comment); header.setParameterId(forecastData.parameter); contentHandler.setValue('.', forecastData.value); header.setUnit(forecastData.unit); long rawTimeZoneOffset = TimeZoneUtils.parseRawTimeZoneOffset(forecastData.localTime.substring(forecastData.localTime.length() - 6)); contentHandler.setTime(TimeZoneUtils.getTimeZone(rawTimeZoneOffset), "yyyy-MM-dd'T'HH:mm:ss", forecastData.localTime.substring(0, forecastData.localTime.length() - 6)); contentHandler.setTimeSeriesHeader(header); contentHandler.applyCurrentFields(); } } /** * This method skips elements until a start element with the corresponding local name is found and returns true, if not returns false * Warning: if the element is not found the reader is skipped until the end of the document * * @param localName * @return boolean * @throws XMLStreamException */ private boolean goTo(String localName) throws XMLStreamException { do { if (!reader.hasNext()) return false; reader.next(); } while (!reader.isStartElement() || !TextUtils.equals(reader.getLocalName(), localName)); return true; } }