diff --git a/src/de/schildbach/pte/SbbProvider.java b/src/de/schildbach/pte/SbbProvider.java index e981d22e..1f5676dc 100644 --- a/src/de/schildbach/pte/SbbProvider.java +++ b/src/de/schildbach/pte/SbbProvider.java @@ -18,8 +18,6 @@ package de.schildbach.pte; import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -28,12 +26,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import de.schildbach.pte.dto.Connection; import de.schildbach.pte.dto.Departure; -import de.schildbach.pte.dto.GetConnectionDetailsResult; -import de.schildbach.pte.dto.Location; -import de.schildbach.pte.dto.LocationType; -import de.schildbach.pte.dto.QueryConnectionsResult; import de.schildbach.pte.dto.QueryDeparturesResult; import de.schildbach.pte.dto.QueryDeparturesResult.Status; import de.schildbach.pte.util.ParserUtils; @@ -57,7 +50,7 @@ public class SbbProvider extends AbstractHafasProvider public boolean hasCapabilities(final Capability... capabilities) { for (final Capability capability : capabilities) - if (capability == Capability.DEPARTURES) + if (capability == Capability.DEPARTURES || capability == Capability.CONNECTIONS) return true; return false; @@ -71,302 +64,6 @@ public class SbbProvider extends AbstractHafasProvider return String.format(NEARBY_URI, stationId); } - private String connectionsQueryUri(final Location from, final Location via, final Location to, final Date date, final boolean dep) - { - final DateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yy"); - final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm"); - final StringBuilder uri = new StringBuilder(); - - uri.append(API_BASE).append("query.exe/dox"); - uri.append("?OK"); - uri.append("&REQ0HafasMaxChangeTime=120"); - uri.append("&REQ0HafasOptimize1=").append(ParserUtils.urlEncode("1:1")); - uri.append("&REQ0HafasSearchForw=").append(dep ? "1" : "0"); - uri.append("&REQ0HafasSkipLongChanges=1"); - uri.append("&REQ0JourneyDate=").append(ParserUtils.urlEncode(DATE_FORMAT.format(date))); - uri.append("&REQ0JourneyStopsS0G=").append(ParserUtils.urlEncode(locationValue(from))); - uri.append("&REQ0JourneyStopsS0A=").append(locationTypeValue(from)); - uri.append("&REQ0JourneyStopsS0ID="); - if (via != null) - { - uri.append("&REQ0JourneyStops1.0G=").append(ParserUtils.urlEncode(locationValue(via))); - uri.append("&REQ0JourneyStops1.0A=").append(locationTypeValue(via)); - uri.append("&REQ0JourneyStops1.0ID="); - } - uri.append("&REQ0JourneyStopsZ0G=").append(ParserUtils.urlEncode(locationValue(to))); - uri.append("&REQ0JourneyStopsZ0A=").append(locationTypeValue(to)); - uri.append("&REQ0JourneyStopsZ0ID="); - uri.append("&REQ0JourneyTime=").append(ParserUtils.urlEncode(TIME_FORMAT.format(date))); - uri.append("&queryPageDisplayed=yes"); - - // TODO products - - uri.append("&start=Suchen"); - - return uri.toString(); - } - - private static int locationTypeValue(final Location location) - { - final LocationType type = location.type; - if (type == LocationType.STATION) - return 1; - if (type == LocationType.ADDRESS) - return 2; - if (type == LocationType.ANY) - return 7; - throw new IllegalArgumentException(type.toString()); - } - - private static String locationValue(final Location location) - { - if (location.type == LocationType.STATION && location.id != 0) - return Integer.toString(location.id); - else - return location.name; - } - - private static final Pattern P_PRE_ADDRESS = Pattern.compile( - "", Pattern.DOTALL); - private static final Pattern P_ADDRESSES = Pattern.compile("]*>\\s*(.*?)\\s*", Pattern.DOTALL); - private static final Pattern P_CHECK_CONNECTIONS_ERROR = Pattern - .compile("(mehrfach vorhanden oder identisch)|(keine Verbindung gefunden werden)|(liegt nach dem Ende der Fahrplanperiode|liegt vor Beginn der Fahrplanperiode)"); - - @Override - public QueryConnectionsResult queryConnections(final Location from, final Location via, final Location to, final Date date, final boolean dep, - final String products, final WalkSpeed walkSpeed) throws IOException - { - final String uri = connectionsQueryUri(from, via, to, date, dep); - final CharSequence page = ParserUtils.scrape(uri); - - final Matcher mError = P_CHECK_CONNECTIONS_ERROR.matcher(page); - if (mError.find()) - { - if (mError.group(1) != null) - return QueryConnectionsResult.TOO_CLOSE; - if (mError.group(2) != null) - return QueryConnectionsResult.NO_CONNECTIONS; - if (mError.group(3) != null) - return QueryConnectionsResult.INVALID_DATE; - } - - List fromAddresses = null; - List viaAddresses = null; - List toAddresses = null; - - // FIXME cannot parse ambiguous - - final Matcher mPreAddress = P_PRE_ADDRESS.matcher(page); - while (mPreAddress.find()) - { - final String type = mPreAddress.group(1); - final String options = mPreAddress.group(2); - - final Matcher mAddresses = P_ADDRESSES.matcher(options); - final List addresses = new ArrayList(); - while (mAddresses.find()) - { - final String address = ParserUtils.resolveEntities(mAddresses.group(1)).trim(); - if (!addresses.contains(address)) - addresses.add(new Location(LocationType.ANY, 0, 0, 0, address + "!")); - } - - if (type.equals("REQ0JourneyStopsS0K")) - fromAddresses = addresses; - else if (type.equals("REQ0JourneyStopsZ0K")) - toAddresses = addresses; - else if (type.equals("REQ0JourneyStops1.0K")) - viaAddresses = addresses; - else - throw new IOException(type); - } - - if (fromAddresses != null || viaAddresses != null || toAddresses != null) - return new QueryConnectionsResult(fromAddresses, viaAddresses, toAddresses); - else - return queryConnections(uri, page); - } - - @Override - public QueryConnectionsResult queryMoreConnections(final String uri) throws IOException - { - final CharSequence page = ParserUtils.scrape(uri); - - return queryConnections(uri, page); - } - - private static final Pattern P_CONNECTIONS_HEAD = Pattern.compile(".*?" // - + "Von: ([^<]*)<.*?" // from - + "Nach: ([^<]*)<.*?" // to - + "Datum: .., (\\d{2}\\.\\d{2}\\.\\d{2}).*?" // currentDate - + "(

.*?" // body - + "(?:\n" // - + "(.*?)

\n", Pattern.DOTALL); - private static final Pattern P_CONNECTIONS_FINE = Pattern.compile("" // - + "(?:]*>)?" // - + "" // link - + "(\\d{1,2}:\\d{2})-(\\d{1,2}:\\d{2}).*?" // departureTime, arrivalTime - , Pattern.DOTALL); - - private QueryConnectionsResult queryConnections(final String uri, final CharSequence page) throws IOException - { - final Matcher mHead = P_CONNECTIONS_HEAD.matcher(page); - if (mHead.matches()) - { - final Location from = new Location(LocationType.ANY, 0, 0, 0, ParserUtils.resolveEntities(mHead.group(1))); - final Location to = new Location(LocationType.ANY, 0, 0, 0, ParserUtils.resolveEntities(mHead.group(2))); - final Date currentDate = ParserUtils.parseDate(mHead.group(3)); - final String body = mHead.group(4); - final String linkEarlier = mHead.group(5) != null ? ParserUtils.resolveEntities(mHead.group(5)) : null; - final String linkLater = mHead.group(6) != null ? ParserUtils.resolveEntities(mHead.group(6)) : null; - final List connections = new ArrayList(); - String oldZebra = null; - - final Matcher mCoarse = P_CONNECTIONS_COARSE.matcher(body); - while (mCoarse.find()) - { - final String zebra = mCoarse.group(1); - if (oldZebra != null && zebra.equals(oldZebra)) - throw new IllegalArgumentException("missed row? last:" + zebra); - else - oldZebra = zebra; - - final String set = mCoarse.group(2); - final Matcher mFine = P_CONNECTIONS_FINE.matcher(set); - if (mFine.matches()) - { - final String link = ParserUtils.resolveEntities(mFine.group(1)); - Date departureTime = ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mFine.group(2))); - if (!connections.isEmpty()) - { - final long diff = ParserUtils.timeDiff(departureTime, connections.get(connections.size() - 1).departureTime); - if (diff > PARSER_DAY_ROLLOVER_THRESHOLD_MS) - departureTime = ParserUtils.addDays(departureTime, -1); - else if (diff < -PARSER_DAY_ROLLOVER_THRESHOLD_MS) - departureTime = ParserUtils.addDays(departureTime, 1); - } - Date arrivalTime = ParserUtils.joinDateTime(currentDate, ParserUtils.parseTime(mFine.group(3))); - if (departureTime.after(arrivalTime)) - arrivalTime = ParserUtils.addDays(arrivalTime, 1); - - connections.add(new Connection(extractConnectionId(link), link, departureTime, arrivalTime, null, null, 0, from.name, 0, to.name, - null)); - } - else - { - throw new IllegalArgumentException("cannot parse '" + set + "' on " + uri); - } - } - - return new QueryConnectionsResult(uri, from, null, to, linkEarlier, linkLater, connections); - } - else - { - throw new IOException(page.toString()); - } - } - - private static final Pattern P_CONNECTION_DETAILS_HEAD = Pattern.compile(".*?" // - + "

\n- ([^<]*) -\n

\n" // - + "(.*?)" // - + "

\n" // - + "Abfahrt: (\\d+\\.\\d+\\.\\d+).*?", Pattern.DOTALL); - private static final Pattern P_CONNECTION_DETAILS_COARSE = Pattern.compile("\\G" // - + "

\n(.*?)

\n" // - + "

\n(.*?)

\n" // - , Pattern.DOTALL); - private static final Pattern P_CONNECTION_DETAILS_JOURNEY = Pattern.compile("" // - + "(?:" // - + "(.+?)\n.*?" // line - + "ab (\\d{1,2}:\\d{2})\n" // departureTime - + "(?: (Gl\\. .+?)\\s*\n)?" // departurePosition - + ".*?" // - + "an (\\d{1,2}:\\d{2})\n" // arrivalTime - + "(?: (Gl\\. .+?)\\s*\n)?" // arrivalPosition - + "|" // - + "(?:Fussweg|Übergang)\n" // - + "(\\d+) Min\\.\n" // minutes - + ")" // - , Pattern.DOTALL); - private static final Pattern P_CONNECTION_DETAILS_DESTINATION = Pattern.compile("" // - + "- ([^<]*) -\n" // destination - , Pattern.DOTALL); - - @Override - public GetConnectionDetailsResult getConnectionDetails(final String uri) throws IOException - { - final CharSequence page = ParserUtils.scrape(uri); - - final Matcher mHead = P_CONNECTION_DETAILS_HEAD.matcher(page); - if (mHead.matches()) - { - Date firstDepartureTime = null; - final String firstDeparture = ParserUtils.resolveEntities(mHead.group(1)); - Date lastArrivalTime = null; - String departure = firstDeparture; - final String body = mHead.group(2); - final Date date = ParserUtils.parseDate(mHead.group(3)); - - final List parts = new ArrayList(4); - - final Matcher mCoarse = P_CONNECTION_DETAILS_COARSE.matcher(body); - while (mCoarse.find()) - { - final Matcher mJourney = P_CONNECTION_DETAILS_JOURNEY.matcher(mCoarse.group(1)); - final Matcher mDestination = P_CONNECTION_DETAILS_DESTINATION.matcher(mCoarse.group(2)); - if (mJourney.matches() && mDestination.matches()) - { - final String arrival = mDestination.group(1); - - if (mJourney.group(6) == null) - { - final String line = normalizeLine(ParserUtils.resolveEntities(mJourney.group(1))); - Date departureTime = ParserUtils.joinDateTime(date, ParserUtils.parseTime(mJourney.group(2))); - if (lastArrivalTime != null && departureTime.before(lastArrivalTime)) - departureTime = ParserUtils.addDays(departureTime, 1); - final String departurePosition = mJourney.group(3) != null ? ParserUtils.resolveEntities(mJourney.group(3)) : null; - Date arrivalTime = ParserUtils.joinDateTime(date, ParserUtils.parseTime(mJourney.group(4))); - if (departureTime.after(arrivalTime)) - arrivalTime = ParserUtils.addDays(arrivalTime, 1); - final String arrivalPosition = mJourney.group(5) != null ? ParserUtils.resolveEntities(mJourney.group(5)) : null; - - parts.add(new Connection.Trip(line, lineColors(line), 0, null, departureTime, departurePosition, 0, departure, arrivalTime, - arrivalPosition, 0, arrival)); - - if (firstDepartureTime == null) - firstDepartureTime = departureTime; - lastArrivalTime = arrivalTime; - departure = arrival; - } - else - { - final int min = Integer.parseInt(mJourney.group(6)); - - parts.add(new Connection.Footway(min, 0, departure, 0, arrival, 0, 0)); - - departure = arrival; - } - } - else - { - throw new IllegalArgumentException("cannot parse '" + mCoarse.group(1) + "', '" + mCoarse.group(2) + "' on " + uri); - } - } - - return new GetConnectionDetailsResult(new Date(), new Connection(AbstractHafasProvider.extractConnectionId(uri), uri, firstDepartureTime, - lastArrivalTime, null, null, 0, firstDeparture, 0, departure, parts)); - } - else - { - throw new IllegalArgumentException("cannot parse '" + page + "' on " + uri); - } - - } - private String departuresQueryUri(final String stationId, final int maxDepartures) { final StringBuilder uri = new StringBuilder(); @@ -417,11 +114,11 @@ public class SbbProvider extends AbstractHafasProvider { // messages if (mHeadCoarse.group(3) != null) - return new QueryDeparturesResult( Status.NO_INFO, Integer.parseInt(stationId)); + return new QueryDeparturesResult(Status.NO_INFO, Integer.parseInt(stationId)); else if (mHeadCoarse.group(5) != null) - return new QueryDeparturesResult( Status.INVALID_STATION, Integer.parseInt(stationId)); + return new QueryDeparturesResult(Status.INVALID_STATION, Integer.parseInt(stationId)); else if (mHeadCoarse.group(6) != null) - return new QueryDeparturesResult( Status.SERVICE_DOWN, Integer.parseInt(stationId)); + return new QueryDeparturesResult(Status.SERVICE_DOWN, Integer.parseInt(stationId)); final String head = mHeadCoarse.group(1) + mHeadCoarse.group(4); final Matcher mHeadFine = P_DEPARTURES_HEAD_FINE.matcher(head);