Add two more gases.
[dive.git] / src / net / ametros / dive / parser / DDPlanParser.java
blobf6ecb8428ebc1167ead32e75a2d45e5684d0af2d
1 /* Ametros Dive Computer
2 * Copyright (C) 2010 Geoff Johnstone
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.ametros.dive.parser;
20 import net.ametros.dive.data.Dive;
21 import net.ametros.dive.data.Gas;
22 import net.ametros.dive.data.Stop;
24 import java.io.File;
25 import java.io.FileReader;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import javax.swing.text.MutableAttributeSet;
32 import javax.swing.text.html.HTML;
33 import javax.swing.text.html.HTMLEditorKit;
34 import javax.swing.text.html.parser.ParserDelegator;
37 /** Parser for DDPlan 2.x HTML dive plans. */
38 public class DDPlanParser extends AbstractParser
40 private static final Pattern depthPattern = Pattern.compile ("^(\\d+)m$");
42 private static final Pattern stopPattern =
43 Pattern.compile ("^(\\d+)\\s*/\\s*\\d+$");
46 @Override
47 protected void doParse (File file) throws IOException
49 FileReader fr = null;
51 try
53 fr = new FileReader (file);
54 new ParserDelegator().parse (fr, new ParserCallback(), true);
56 finally
58 if (null != fr)
59 fr.close();
64 enum ParserState
66 INITIAL, // Look for <table>.
67 FIND_DEPTH, // First text gives dive depth, then START_DIVE.
68 START_DIVE, // Look for <table>.
69 TITLES, // Texts are column names, then look for </tr>. Special
70 // column names are Gas, ppO2, D\RT; others are durations.
71 STOPS, // Texts are column values, then look for Text CNS%.
72 GRADIENTS; // Text starting "Gradient Factors", then INITIAL.
76 class ParserCallback extends HTMLEditorKit.ParserCallback
78 private ParserState state;
79 private int diveDepth;
80 private final List<String> columns = new ArrayList<String>();
81 private Dive[] dives;
82 private int nextCol;
83 private Gas stopGas;
84 private int stopDepth;
87 public ParserCallback()
89 reset();
93 public void reset()
95 state = ParserState.INITIAL;
96 diveDepth = 0;
97 columns.clear();
98 dives = null;
99 nextCol = 0;
100 stopGas = null;
101 stopDepth = 0;
105 @Override
106 public void handleEndTag (HTML.Tag tag, int pos)
108 // System.err.println ("End " + tag);
110 switch (state)
112 case TITLES:
113 if (HTML.Tag.TR.equals (tag))
115 dives = new Dive[columns.size()];
116 int i = 0;
118 for (String str: columns)
120 if (!"Gas".equals (str) &&
121 !"ppO2".equals (str) &&
122 !"D\\RT".equals (str))
124 final int duration = Integer.parseInt (str);
125 dives[i] = createDive (diveDepth, duration, getDefaultGas());
128 ++i;
131 state = ParserState.STOPS;
134 break;
136 default:
137 break;
142 @Override
143 public void handleStartTag (HTML.Tag tag, MutableAttributeSet a, int pos)
145 // System.err.println ("Start " + tag);
147 switch (state)
149 case INITIAL:
150 if (HTML.Tag.TABLE.equals (tag))
151 state = ParserState.FIND_DEPTH;
152 break;
154 case START_DIVE:
155 if (HTML.Tag.TABLE.equals (tag))
156 state = ParserState.TITLES;
157 break;
159 default:
160 break;
165 @Override
166 public void handleText (char[] data, int pos)
168 final String text = normaliseWhitespace (data);
169 // System.err.println ("Text: " + text);
171 switch (state)
173 case FIND_DEPTH:
174 final Matcher m0 = depthPattern.matcher (text);
175 if (!m0.matches())
176 throw new RuntimeException ("Expected dive depth; got " + text);
178 diveDepth = Integer.parseInt (m0.group (1));
179 state = ParserState.START_DIVE;
180 break;
182 case TITLES:
183 columns.add (text);
184 break;
186 case STOPS:
187 if (text.startsWith ("CNS%"))
188 state = ParserState.GRADIENTS;
189 else
191 if (nextCol == columns.size())
193 stopGas = null;
194 stopDepth = 0;
195 nextCol = 0;
198 final String key = columns.get (nextCol);
199 if ("Gas".equals (key))
201 stopGas = parseGas (text);
204 else if ("D\\RT".equals (key))
206 stopDepth = Integer.parseInt (text);
209 else if (!"ppO2".equals (key) && !text.isEmpty())
211 final Matcher m1 = stopPattern.matcher (text);
212 if (!m1.matches())
213 throw new RuntimeException ("Expected stop; got " + text);
215 final Stop stop = of.createStop();
216 stop.setDepth (stopDepth);
217 stop.setDuration (Integer.parseInt (m1.group (1)));
218 stop.setGas (stopGas);
219 dives[nextCol].getAscent().getStops().add (stop);
222 ++nextCol;
225 break;
227 case GRADIENTS:
228 if (text.startsWith ("Gradient Factors"))
230 // TODO: WTF are gradient factors?
232 for (Dive dive: dives)
233 if (null != dive)
234 addDive (dive);
236 reset();
239 break;
241 default:
242 break;