sascha@197: package de.intevation.flys.importer; sascha@197: sascha@199: import java.util.ArrayList; sascha@489: import java.util.HashSet; sascha@199: sascha@197: import java.io.File; sascha@197: import java.io.IOException; sascha@199: import java.io.LineNumberReader; sascha@199: import java.io.InputStreamReader; sascha@199: import java.io.FileInputStream; sascha@199: sascha@199: import java.text.NumberFormat; sascha@199: sascha@199: import org.apache.log4j.Logger; sascha@199: sascha@199: import de.intevation.flys.utils.StringUtil; sascha@197: sascha@200: import java.util.regex.Pattern; sascha@200: import java.util.regex.Matcher; sascha@200: sascha@201: import java.math.BigDecimal; sascha@201: sascha@197: public class WstParser sascha@197: { sascha@199: private static Logger log = Logger.getLogger(WstParser.class); sascha@199: sascha@199: public static final String COLUMN_BEZ_TEXT = "column-bez-text"; sascha@199: public static final String COLUMN_BEZ_BREITE = "column-bez-breite"; sascha@199: public static final String COLUMN_QUELLE = "column-quelle"; sascha@199: public static final String COLUMN_DATUM = "column-datum"; sascha@199: sascha@474: public static final BigDecimal UNDEFINED_ZERO = sascha@474: new BigDecimal(0.0); sascha@474: public static final BigDecimal MIN_RANGE = sascha@474: new BigDecimal(-Double.MAX_VALUE); sascha@474: public static final BigDecimal MAX_RANGE = sascha@474: new BigDecimal(Double.MAX_VALUE); sascha@199: sascha@199: public static final String ENCODING = "ISO-8859-1"; sascha@199: sascha@200: public static final Pattern UNIT_COMMENT = sascha@200: Pattern.compile("\\*\\s*[kK][mM]\\s+(.+)"); sascha@200: sascha@200: public static final Pattern UNIT = sascha@200: Pattern.compile("[^\\[]*\\[([^]]+)\\].*"); sascha@200: sascha@753: public static final BigDecimal INTERVAL_GAP = sascha@753: new BigDecimal(0.00001); sascha@753: sascha@201: protected ImportWst wst; sascha@201: sascha@753: protected ImportRange lastRange; sascha@753: sascha@197: public WstParser() { sascha@197: } sascha@197: sascha@201: public ImportWst getWst() { sascha@201: return wst; sascha@201: } sascha@201: sascha@201: public void setWst(ImportWst wst) { sascha@201: this.wst = wst; sascha@201: } sascha@201: sascha@197: public void parse(File file) throws IOException { sascha@199: sascha@199: log.info("Parsing WST file '" + file + "'"); sascha@199: sascha@201: wst = new ImportWst(file.getName()); sascha@201: sascha@199: LineNumberReader in = null; sascha@199: try { sascha@199: in = sascha@199: new LineNumberReader( sascha@199: new InputStreamReader( sascha@199: new FileInputStream(file), ENCODING)); sascha@199: sascha@199: String input; sascha@199: boolean first = true; sascha@199: int columnCount = 0; sascha@199: sascha@199: String [] lsBezeichner = null; sascha@199: String [] langBezeichner = null; sascha@199: int [] colNaWidths = null; sascha@199: String [] quellen = null; sascha@199: String [] daten = null; sascha@199: sascha@474: BigDecimal [] aktAbfluesse = null; sascha@474: BigDecimal [] firstAbfluesse = null; sascha@474: sascha@474: BigDecimal minKm = MAX_RANGE; sascha@474: BigDecimal maxKm = MIN_RANGE; sascha@199: sascha@199: boolean columnHeaderChecked = false; sascha@199: sascha@200: String einheit = "Wasserstand [NN + m]"; sascha@199: sascha@489: HashSet kms = new HashSet(); sascha@199: sascha@199: while ((input = in.readLine()) != null) { sascha@199: String line = input; sascha@199: if (first) { // fetch number of columns sascha@199: if ((line = line.trim()).length() == 0) { sascha@199: continue; sascha@199: } sascha@199: try { sascha@199: columnCount = Integer.parseInt(line); sascha@199: if (columnCount <= 0) { sascha@199: throw new NumberFormatException( sascha@199: "number columns <= 0"); sascha@199: } sascha@199: log.debug("Number of columns: " + columnCount); sascha@474: wst.setNumberColumns(columnCount); sascha@199: lsBezeichner = new String[columnCount]; sascha@199: } sascha@199: catch (NumberFormatException nfe) { sascha@199: log.warn(nfe); sascha@199: continue; sascha@199: } sascha@199: first = false; sascha@199: continue; sascha@199: } sascha@199: sascha@199: line = line.replace(',', '.'); sascha@199: sascha@199: if (line.startsWith("*\u001f")) { sascha@474: BigDecimal [] data = sascha@199: parseLineAsDouble(line, columnCount, false, true); sascha@199: sascha@474: if (aktAbfluesse != null) { sascha@474: addInterval(minKm, maxKm, aktAbfluesse); sascha@474: minKm = MAX_RANGE; sascha@474: maxKm = MIN_RANGE; sascha@199: } sascha@199: sascha@474: aktAbfluesse = new BigDecimal[columnCount]; sascha@474: log.debug("new q range: " + columnCount); sascha@474: for (int i = 0; i < Math.min(columnCount, data.length); ++i) { sascha@474: if (data[i] != null) { sascha@474: log.debug(" column: " + data[i]); sascha@474: aktAbfluesse[i] = data[i]; sascha@199: } sascha@199: } sascha@199: sascha@199: if (firstAbfluesse == null) { sascha@474: firstAbfluesse = (BigDecimal [])aktAbfluesse.clone(); sascha@199: } sascha@199: continue; sascha@199: } sascha@199: sascha@199: if (line.startsWith("*!")) { sascha@199: String spezial = line.substring(2).trim(); sascha@199: sascha@199: if (spezial.length() == 0) { sascha@199: continue; sascha@199: } sascha@199: sascha@199: if (spezial.startsWith(COLUMN_BEZ_TEXT)) { sascha@199: spezial = spezial.substring(COLUMN_BEZ_TEXT.length()).trim(); sascha@199: if (spezial.length() == 0) { sascha@199: continue; sascha@199: } sascha@199: langBezeichner = StringUtil.splitQuoted(spezial, '"'); sascha@199: } sascha@199: else if (spezial.startsWith(COLUMN_BEZ_BREITE)) { sascha@199: spezial = spezial.substring(COLUMN_BEZ_BREITE.length()).trim(); sascha@199: sascha@199: if (spezial.length() == 0) { sascha@199: continue; sascha@199: } sascha@199: sascha@199: String[] split = spezial.split("\\s+"); sascha@199: sascha@199: colNaWidths = new int[split.length]; sascha@199: for (int i=0; i < split.length; i++) { sascha@199: colNaWidths[i] = Integer.parseInt(split[i]); sascha@199: } sascha@199: } sascha@199: else if (spezial.startsWith(COLUMN_QUELLE)) { sascha@199: if (spezial.length() == 0) { sascha@199: continue; sascha@199: } sascha@199: quellen = StringUtil.splitQuoted(spezial, '"'); sascha@199: } sascha@199: else if (spezial.startsWith(COLUMN_DATUM)) { sascha@199: spezial = spezial.substring(COLUMN_DATUM.length()).trim(); sascha@199: if (spezial.length() == 0) { sascha@199: continue; sascha@199: } sascha@199: daten = StringUtil.splitQuoted(spezial, '"'); sascha@199: } sascha@199: continue; sascha@199: } sascha@199: sascha@200: if (line.length() < 11) { sascha@200: continue; sascha@200: } sascha@200: sascha@199: if (line.startsWith("*")) { sascha@200: Matcher m = UNIT_COMMENT.matcher(line); sascha@200: if (m.matches()) { sascha@200: log.debug("unit comment found"); sascha@464: // XXX: This hack is needed because desktop sascha@200: // FLYS is broken figuring out the unit sascha@200: String [] units = m.group(1).split("\\s{2,}"); sascha@200: m = UNIT.matcher(units[0]); sascha@200: einheit = m.matches() ? m.group(1) : units[0]; sascha@200: log.debug("unit: " + einheit); sascha@199: } sascha@199: continue; sascha@199: } sascha@199: sascha@199: if (firstAbfluesse != null) { sascha@199: if (!columnHeaderChecked) { sascha@199: int unknownCount = 0; sascha@495: HashSet uniqueColumnNames = sascha@495: new HashSet(); sascha@474: for (int i = 0; i < lsBezeichner.length; ++i) { sascha@199: if (lsBezeichner[i] == null sascha@199: || lsBezeichner[i].length() == 0) { sascha@474: double q = firstAbfluesse[i].doubleValue(); sascha@199: if (q < 0.001) { sascha@199: lsBezeichner[i] = sascha@200: ""; sascha@199: ++unknownCount; sascha@199: } sascha@199: else { sascha@199: lsBezeichner[i] = "Q="+format(q); sascha@199: } sascha@199: } sascha@495: String candidate = lsBezeichner[i]; sascha@495: int collision = 1; sascha@495: while (!uniqueColumnNames.add(candidate)) { sascha@495: candidate = lsBezeichner[i] + sascha@495: " (" + collision + ")"; sascha@495: ++collision; sascha@495: } sascha@495: wst.getColumn(i).setName(candidate); sascha@199: } sascha@199: columnHeaderChecked = true; sascha@199: } sascha@199: sascha@474: BigDecimal [] data = sascha@199: parseLineAsDouble(line, columnCount, true, false); sascha@199: sascha@474: BigDecimal kaem = data[0]; sascha@199: sascha@489: if (!kms.add(kaem)) { sascha@489: log.warn( sascha@489: "km " + kaem + sascha@489: " (line " + in.getLineNumber() + sascha@489: ") found more than once. -> ignored"); sascha@489: continue; sascha@489: } sascha@489: sascha@474: if (kaem.compareTo(minKm) < 0) { sascha@199: minKm = kaem; sascha@199: } sascha@474: if (kaem.compareTo(maxKm) > 0) { sascha@199: maxKm = kaem; sascha@199: } sascha@199: sascha@199: // extract values sascha@199: for (int i = 0; i < columnCount; ++i) { sascha@481: addValue(kaem, data[i+1], i); sascha@199: } sascha@199: sascha@199: } sascha@199: else { // firstAbfluesse == null sascha@199: if (langBezeichner != null) { sascha@199: lsBezeichner = StringUtil.fitArray( sascha@199: langBezeichner, lsBezeichner); sascha@199: } sascha@199: else if (colNaWidths != null) { sascha@199: for (int j = 0, i = 0, N = input.length(); sascha@199: j < colNaWidths.length && i < N; sascha@199: i += colNaWidths[j++] sascha@199: ) { sascha@199: lsBezeichner[j] = input.substring( sascha@199: i, i+colNaWidths[j]).trim(); sascha@199: } sascha@199: } sascha@199: else { sascha@199: // first column begins at position 8 in line sascha@199: for (int i = 8, col = 0; i < input.length(); i += 9) { sascha@199: if ((i + 9) > input.length()) { sascha@199: i = input.length() - 10; sascha@199: } sascha@199: // one column header is 9 chars wide sascha@199: lsBezeichner[col++] = sascha@199: input.substring(i, i + 9).trim(); sascha@199: sascha@199: if (col == lsBezeichner.length) { sascha@199: break; sascha@199: } sascha@199: } sascha@199: } sascha@199: } sascha@199: sascha@199: } sascha@474: addInterval(minKm, maxKm, aktAbfluesse); sascha@199: } sascha@199: finally { sascha@199: if (in != null) { sascha@199: in.close(); sascha@199: } sascha@199: } sascha@199: } sascha@199: sascha@474: protected void addValue(BigDecimal km, BigDecimal w, int index) { sascha@474: if (w != null) { sascha@474: ImportWstColumn column = wst.getColumn(index); sascha@474: column.addColumnValue(km, w); sascha@474: } sascha@199: } sascha@199: sascha@201: private static final NumberFormat NF = getNumberFormat(); sascha@201: sascha@202: private static final NumberFormat getNumberFormat() { sascha@199: NumberFormat nf = NumberFormat.getInstance(); sascha@199: nf.setMinimumFractionDigits(2); sascha@199: nf.setMaximumFractionDigits(2); sascha@202: return nf; sascha@201: } sascha@201: sascha@201: protected static String format(double value) { sascha@201: return NF.format(value); sascha@199: } sascha@199: sascha@199: protected void addInterval( sascha@474: BigDecimal from, sascha@474: BigDecimal to, sascha@474: BigDecimal [] values sascha@199: ) { sascha@201: log.debug("addInterval: " + from + " " + to); sascha@201: sascha@474: if (values == null || from == MAX_RANGE) { sascha@199: return; sascha@199: } sascha@201: sascha@474: if (to.compareTo(from) < 0) { sascha@474: BigDecimal t = from; from = to; to = t; sascha@474: } sascha@201: sascha@474: ImportRange range = new ImportRange(from, to); sascha@201: sascha@753: // little workaround to make the q ranges tightly fit. sascha@753: // Leave a very small gap to ensure that the range queries sascha@753: // still work. sascha@753: sascha@753: if (lastRange != null) { sascha@753: double d1 = Math.abs( sascha@753: lastRange.getB().doubleValue() - range.getA().doubleValue()); sascha@753: double d2 = Math.abs( sascha@753: range.getB().doubleValue() - lastRange.getA().doubleValue()); sascha@753: sascha@753: if (d1 < d2) { sascha@753: lastRange.setB(range.getA().subtract(INTERVAL_GAP)); sascha@753: } sascha@753: else { sascha@753: range.setA(lastRange.getB().subtract(INTERVAL_GAP)); sascha@753: } sascha@753: } sascha@753: sascha@474: for (int i = 0; i < values.length; ++i) { sascha@474: ImportWstColumn column = wst.getColumn(i); sascha@474: ImportWstQRange wstQRange = new ImportWstQRange(range, values[i]); sascha@201: column.addColumnQRange(wstQRange); sascha@200: } sascha@753: sascha@753: lastRange = range; sascha@199: } sascha@199: sascha@474: private static final BigDecimal [] parseLineAsDouble( sascha@464: String line, sascha@464: int count, sascha@199: boolean bStation, sascha@199: boolean bParseEmptyAsZero sascha@199: ) { sascha@199: String [] tokens = parseLine(line, count, bStation); sascha@199: sascha@474: BigDecimal [] doubles = new BigDecimal[tokens.length]; sascha@199: sascha@199: for (int i = 0; i < doubles.length; ++i) { sascha@199: String token = tokens[i].trim(); sascha@199: if (token.length() != 0) { sascha@474: doubles[i] = new BigDecimal(token); sascha@199: } sascha@199: else if (bParseEmptyAsZero) { sascha@199: doubles[i] = UNDEFINED_ZERO; sascha@199: } sascha@199: } sascha@199: sascha@199: return doubles; sascha@199: } sascha@199: sascha@199: private static String [] parseLine( sascha@464: String line, sascha@199: int tokenCount, sascha@199: boolean bParseStation sascha@199: ) { sascha@199: ArrayList strings = new ArrayList(); sascha@199: sascha@199: if (bParseStation) { sascha@199: if (line.length() < 8) { sascha@199: throw new IllegalArgumentException("station too short"); sascha@199: } sascha@199: strings.add(line.substring(0, 8)); sascha@199: } sascha@199: sascha@199: int pos = 9; sascha@199: for (int i = 0; i < tokenCount; ++i) { sascha@199: if (line.length() >= pos + 8) { sascha@199: strings.add(line.substring(pos, pos + 8)); sascha@199: } sascha@199: else { sascha@199: strings.add(""); sascha@199: } sascha@199: pos += 9; sascha@199: } sascha@199: sascha@199: return strings.toArray(new String[strings.size()]); sascha@197: } sascha@197: } sascha@197: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :