diff --git a/src/de/schildbach/pte/BahnProvider.java b/src/de/schildbach/pte/BahnProvider.java index 479a4a62..c6a34507 100644 --- a/src/de/schildbach/pte/BahnProvider.java +++ b/src/de/schildbach/pte/BahnProvider.java @@ -30,14 +30,13 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import de.schildbach.pte.QueryDeparturesResult.Status; - /** * @author Andreas Schildbach */ public final class BahnProvider implements NetworkProvider { public static final String NETWORK_ID = "mobile.bahn.de"; + private static final String API_BASE = "http://mobile.bahn.de/bin/mobil/"; private static final long PARSER_DAY_ROLLOVER_THRESHOLD_MS = 12 * 60 * 60 * 1000; @@ -50,7 +49,7 @@ public final class BahnProvider implements NetworkProvider return true; } - private static final String NAME_URL = "http://mobile.bahn.de/bin/mobil/bhftafel.exe/dox?input="; + private static final String NAME_URL = API_BASE + "bhftafel.exe/dox?input="; private static final Pattern P_SINGLE_NAME = Pattern.compile(".*.*", Pattern.DOTALL); private static final Pattern P_MULTI_NAME = Pattern.compile("", Pattern.DOTALL); @@ -87,8 +86,8 @@ public final class BahnProvider implements NetworkProvider final List stations = new ArrayList(); - final String url = "http://mobile.bahn.de/bin/mobil/query.exe/dox" + "?performLocating=2&tpl=stopsnear&look_maxdist=" - + (maxDistance > 0 ? maxDistance : 5000) + "&look_stopclass=1023" + "&look_x=" + latLonToInt(lon) + "&look_y=" + latLonToInt(lat); + final String url = API_BASE + "query.exe/dox" + "?performLocating=2&tpl=stopsnear&look_maxdist=" + (maxDistance > 0 ? maxDistance : 5000) + + "&look_stopclass=1023" + "&look_x=" + latLonToInt(lon) + "&look_y=" + latLonToInt(lat); final CharSequence page = ParserUtils.scrape(url); final Matcher m = P_NEARBY_STATIONS.matcher(page); @@ -133,7 +132,7 @@ public final class BahnProvider implements NetworkProvider final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm"); final StringBuilder uri = new StringBuilder(); - uri.append("http://mobile.bahn.de/bin/mobil/query.exe/dox"); + uri.append(API_BASE).append("query.exe/dox"); uri.append("?REQ0HafasOptimize1=0:1"); uri.append("&REQ0JourneyStopsS0G=").append(ParserUtils.urlEncode(from)); uri.append("&REQ0JourneyStopsS0A=").append(locationType(fromType)); @@ -433,132 +432,88 @@ public final class BahnProvider implements NetworkProvider public String departuresQueryUri(final String stationId, final int maxDepartures) { final StringBuilder uri = new StringBuilder(); - uri.append("http://mobile.bahn.de/bin/mobil/bhftafel.exe/dox"); - uri.append("?start="); - uri.append("&rt=1"); - if (maxDepartures != 0) - uri.append("&maxJourneys=").append(maxDepartures); - uri.append("&boardType=Abfahrt"); - uri.append("&productsFilter=1111111111000000"); + uri.append(API_BASE).append("bhftafel.exe/dn"); + uri.append("?productsFilter=11111111111111"); + uri.append("&boardType=dep"); + uri.append("&maxJourneys=").append(maxDepartures != 0 ? maxDepartures : 50); // default taken from railnavigator + uri.append("&start=yes"); + uri.append("&L=vs_java3"); uri.append("&input=").append(stationId); return uri.toString(); } - private static final Pattern P_DEPARTURES_HEAD_COARSE = Pattern.compile(".*?" // - + "(?:" // - + "Deutsche Bahn - Abfahrt.*?(.*?Abfahrt.*?)" // - + "|(Eingabe kann nicht interpretiert))" // - + ".*?" // - , Pattern.DOTALL); - private static final Pattern P_DEPARTURES_HEAD_FINE = Pattern.compile(".*?" // - + "
\n\n(.+?)\\s*(?:- Aktuell)?\n.*?" // location - + "Abfahrt (\\d{1,2}:\\d{2})\nUhr, (\\d{2}\\.\\d{2}\\.\\d{2}).*?" // currentTime - , Pattern.DOTALL); - private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("
\n(.+?)
", Pattern.DOTALL); - static final Pattern P_DEPARTURES_FINE = Pattern.compile(".*?" // - + "\n" // lineLink - + "(.*?)\n" // line - + "\n" // - + ">>\n" // - + "\\s*(.+?)\\s*\n" // destination - + "
\n" // - + "(\\d{1,2}:\\d{2})" // time - + "(?: (?:(pünktl\\.)|ca. \\+(\\d+)))?" // ontime, delay - + "(?: k\\.A\\.)?" // - + "(?:, ([^<]*))*" // messages FIXME - + "(?:(?:, )?(?:heute )?(Gl\\. " + ParserUtils.P_PLATFORM + ")(?:\\s*)?)?" // position - + "(?:,
]*>[^<]*)?" // (ersatzzug message) - + "(?:,
[^<]*]*>[^<]*)?" // (ersatzzug message) - + "(?:,
 [^<]*)?" // (sonderzug message) - , Pattern.DOTALL); - private static final Pattern P_DEPARTURES_URI_STATION_ID = Pattern.compile("input=(\\d+)"); + private static final Pattern P_DEPARTURES_COARSE = Pattern.compile("", Pattern.DOTALL); + static final Pattern P_DEPARTURES_FINE = Pattern.compile("" // + + "fpTime=\"(\\d{1,2}:\\d{2})\" fpDate=\"(\\d{2}\\.\\d{2}\\.\\d{2})\" \n" // time, date + + "delay=\"(?:-|k\\.A\\.?|cancel|\\+?\\s*(\\d+))\" \n" // delay + + "(?:platform =\"([^\"]*)\" \n)?" // position + + "(?:newpl =\"([^\"]*)\" \n)?" // + + "targetLoc=\"([^\"]*)\" \n" // destination + + "prod=\"([^\"]*)\" \n" // line + + "(?:dir=[^\n]*\n)?" // (destination) + + "(?:depStation=\"([^\"]*)\"\n)?" // + + "delayReason=\"([^\"]*)\"\n" // message + ); + private static final Pattern P_DEPARTURES_MESSAGES = Pattern.compile(" departures = new ArrayList(8); + final Calendar calendar = new GregorianCalendar(); + + final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(page); + while (mDepCoarse.find()) + { + final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(1)); + if (mDepFine.matches()) { - final String location = ParserUtils.resolveEntities(mHeadFine.group(1)); - final Date currentTime = ParserUtils.joinDateTime(ParserUtils.parseDate(mHeadFine.group(3)), ParserUtils - .parseTime(mHeadFine.group(2))); - final List departures = new ArrayList(8); - - // choose matcher - final Matcher mDepCoarse = P_DEPARTURES_COARSE.matcher(mHeadCoarse.group(1)); - while (mDepCoarse.find()) + if (mDepFine.group(8) == null) { - final Matcher mDepFine = P_DEPARTURES_FINE.matcher(mDepCoarse.group(1)); - if (mDepFine.matches()) + final Date plannedTime = ParserUtils.joinDateTime(ParserUtils.parseDate(mDepFine.group(2)), ParserUtils.parseTime(mDepFine + .group(1))); + + Date predictedTime = null; + if (mDepFine.group(3) != null) { - final String lineLink = ParserUtils.resolveEntities(mDepFine.group(1)); - - final String line = normalizeLine(ParserUtils.resolveEntities(mDepFine.group(2))); - - final String destination = ParserUtils.resolveEntities(mDepFine.group(3)); - - final Calendar current = new GregorianCalendar(); - current.setTime(currentTime); - final Calendar parsed = new GregorianCalendar(); - parsed.setTime(ParserUtils.parseTime(mDepFine.group(4))); - parsed.set(Calendar.YEAR, current.get(Calendar.YEAR)); - parsed.set(Calendar.MONTH, current.get(Calendar.MONTH)); - parsed.set(Calendar.DAY_OF_MONTH, current.get(Calendar.DAY_OF_MONTH)); - if (ParserUtils.timeDiff(parsed.getTime(), currentTime) < -PARSER_DAY_ROLLOVER_THRESHOLD_MS) - parsed.add(Calendar.DAY_OF_MONTH, 1); - final Date plannedTime = parsed.getTime(); - - Date predictedTime = null; - if (mDepFine.group(5) != null) - { - predictedTime = plannedTime; - } - else if (mDepFine.group(6) != null) - { - final int delay = Integer.parseInt(mDepFine.group(6)); - parsed.add(Calendar.MINUTE, delay); - predictedTime = parsed.getTime(); - } - - final String message = ParserUtils.resolveEntities(mDepFine.group(7)); - - final String position = ParserUtils.resolveEntities(mDepFine.group(8)); - - final Departure dep = new Departure(plannedTime, predictedTime, line, line != null ? LINES.get(line.charAt(0)) : null, - lineLink, position, 0, destination, message); - if (!departures.contains(dep)) - departures.add(dep); - } - else - { - throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(1) + "' on " + uri); + calendar.setTime(plannedTime); + calendar.add(Calendar.MINUTE, Integer.parseInt(mDepFine.group(3))); + predictedTime = calendar.getTime(); } + + final String position = mDepFine.group(4) != null ? "Gl. " + ParserUtils.resolveEntities(mDepFine.group(4)) : null; + + final String destination = ParserUtils.resolveEntities(mDepFine.group(6)); + + final String line = normalizeLine(ParserUtils.resolveEntities(mDepFine.group(7))); + + final String message = ParserUtils.resolveEntities(mDepFine.group(9)); + + departures.add(new Departure(plannedTime, predictedTime, line, line != null ? LINES.get(line.charAt(0)) : null, null, position, + 0, destination, message)); } - - return new QueryDeparturesResult(uri, stationId, location, departures); } else { - throw new IllegalArgumentException("cannot parse '" + mHeadCoarse.group(1) + "' on " + uri); + throw new IllegalArgumentException("cannot parse '" + mDepCoarse.group(1) + "' on " + uri); } } - else - { - return new QueryDeparturesResult(uri, Status.NO_INFO); - } + + return new QueryDeparturesResult(uri, 0, null, departures); } private static final Pattern P_NORMALIZE_LINE_NUMBER = Pattern.compile("\\d{2,5}"); diff --git a/test/de/schildbach/pte/live/BahnProviderLiveTest.java b/test/de/schildbach/pte/live/BahnProviderLiveTest.java index 1f090ceb..66d816dd 100644 --- a/test/de/schildbach/pte/live/BahnProviderLiveTest.java +++ b/test/de/schildbach/pte/live/BahnProviderLiveTest.java @@ -24,6 +24,7 @@ import org.junit.Test; import de.schildbach.pte.BahnProvider; import de.schildbach.pte.Connection; import de.schildbach.pte.QueryConnectionsResult; +import de.schildbach.pte.QueryDeparturesResult; import de.schildbach.pte.NetworkProvider.LocationType; import de.schildbach.pte.NetworkProvider.WalkSpeed; @@ -33,6 +34,13 @@ import de.schildbach.pte.NetworkProvider.WalkSpeed; public class BahnProviderLiveTest { private BahnProvider provider = new BahnProvider(); + + @Test + public void departures() throws Exception + { + final QueryDeparturesResult queryDepartures = provider.queryDepartures(provider.departuresQueryUri("692991", 0)); + System.out.println(queryDepartures.departures); + } @Test public void shortConnection() throws Exception