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.Collection;
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;
private String externalForecastTime = 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.nextTagparseIdentifier();
//noinspection SpellCheckingInspectionparseForecastTime();
reader.require(XMLStreamConstants.START_ELEMENT, null, "amocwarning");
reader.nextTag();
reader.require(XMLStreamConstants.START_ELEMENT, null, "sourcewarning-info");
parseIdentifierparseWarningInfo();
reader.require(XMLStreamConstants.START_ELEMENT, null, "warning"parseWarningAreas();
}
reader.nextTag();/**
* Retrieve reader.require(XMLStreamConstants.START_ELEMENT, null, "warning-info");
the identifier from the source element
parseWarningInfo();*
* @throws parseWarningAreas()XMLStreamException;
}
*/**
private *void filterparseIdentifier() thethrows warningXMLStreamException sequence{
and warning title text attributes from the text elements. goTo("identifier");
if (TextUtils.equals(reader.getLocalName(), "identifier")) {
* they will be used forthis.identifier all= areasreader.getElementText();
*
}
* @throws XMLStreamException}
/**/
private void parseWarningInfo() throws XMLStreamException { * Retrieve the identifier from the source element
*
do {
* @throws XMLStreamException;
*/
private ifvoid (!goTo("text","warning-info"))parseForecastTime() throws XMLStreamException {
goTo("issue-time-utc");
// no more text areas, return.if (TextUtils.equals(reader.getLocalName(), "issue-time-utc")) {
this.externalForecastTime return= reader.getElementText();
}
}
goTo("warning");
}
//**
we only need the sequence number* filter the warning sequence and thewarning title.
text attributes from the text elements.
* they if (this.sequenceNumber == null || this.title == null) {will be used for all areas.
*
* @throws XMLStreamException
*/
Stringprivate typevoid = reader.getAttributeValue(null, "type");
parseWarningInfo() throws XMLStreamException {
do {
if (TextUtils.equals!goTo("warning_sequence", typetext","warning-info")) {
// no more text this.sequenceNumber = reader.getElementText();
areas, return.
return;
} else if (TextUtils.equals("warning_title_text", type)) { }
// we only need the sequence number and this.title = reader.getElementText();the title.
if (this.sequenceNumber == null }
|| this.title == null) {
}
String type if (this.sequenceNumber != null && this.title != null) {= reader.getAttributeValue(null, "type");
// we're finished with the text elements, skip to the areas
if (TextUtils.equals("warning_sequence", type)) {
this.sequenceNumber return;= reader.getElementText();
}
} else } whileif (readerTextUtils.hasNext(equals("warning_title_text", type)); {
}
/**
* Retrieve the identifier from thethis.title source element
= reader.getElementText();
*
* @throws javax.xml.stream.XMLStreamException; }
*/ }
private void parseIdentifier() throws XMLStreamException {
if (this.sequenceNumber != null && goTo("identifier");
this.title != null) {
if (TextUtils.equals(reader.getLocalName(), "identifier")) {
this.identifier = reader.getElementText();
// we're finished with the text elements, skip to the areas
}
return;
// only identifier inside the source tag. Jump}
to warning.
} while goTo("warning"(reader.hasNext());
}
/**
* Retrieve the locationId from the area element(s) and call separate method to parse forecast data
*
* @throws javax.xml.stream.XMLStreamException;
*/
private void parseWarningAreas() throws XMLStreamException {
// parse alle areas binnen een warning element.
do {
if (!goTo("area","warning")) {
// alle area's have been processed or an end tag warning has been found.
return;
}
String locationId = reader.getAttributeValue(null, "aac");
// an area can have more that one fore-cast periods.
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
* return true if more forecast periods are available
*
* @throws javax.xml.stream. @throws XMLStreamException;
*/
private void parseForecastData(String locationId) throws XMLStreamException {
// not every area has a forecast-period. Try to find the next forecast-period,
// but stop if not found, or an end-tag area has been found.
while (goTo("forecast-period","area")) {
// but stop if not //found, getor thean start-time-utc
end-tag area has been found.
String externalForecastTime = reader.getAttributeValue(null, "start-time-utc");while (goTo("forecast-period","area")) {
// 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 {
// process all elements until we reach the end tag forecast-period.
if (!goTo("element", "forecast-period")) {
// break so we can write this forecast as a timeseries.
break;
}
String sequence = reader.getAttributeValue(null, "sequence");
if (sequence == null) {
// we only want elements where sequence have been filled.
break;
}
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");
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());
// All events have been collected
writeTimeSerie(locationId, externalForecastTime, forecastSequenceMap.values());
}
}
/**
* Write a time serie header and it's events based on the forecastData.
* @param locationId location of the timeserie
* @param externalForecastTime forecast time.
* @param forecastCollection collection of forecast events.
*/
private void writeTimeSerie(String locationId, String externalForecastTime, Collection<ForecastData> forecastCollection) {
//noinspection SpellCheckingInspection
header.setForecastTime(TimeZoneUtils.GMT, "yyyy-MM-dd'T'HH:mm:ss'Z'", this.externalForecastTime);
header.setLocationId(locationId);
for (ForecastData forecastData : forecastCollection) {
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 local element name
* @return boolean return true if the localname was found.
* @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;
}
/**
* This method skips elements until a start element with the corresponding local name is found and returns true,
* If an end tag with name endName is found, return false.
* Otherwise returns false
* Warning: if the element is not found and the end tag is not found, the reader is skipped until the end of the document
*
* @param localName local name to look for
* @param endName local name of end tag to find.
* @return boolean return true if localName cannot be found or the endName end tag has been found.
* @throws XMLStreamException
*/
private boolean goTo(String localName, String endName) throws XMLStreamException {
do {
if (!reader.hasNext()) return false;
if (reader.isEndElement() && TextUtils.equals(reader.getLocalName(), endName)) return false;
reader.next();
} while (!reader.isStartElement() || !TextUtils.equals(reader.getLocalName(), localName));
return true;
}
}
|