diff options
19 files changed, 1199 insertions, 121 deletions
diff --git a/main/res/values-de/strings.xml b/main/res/values-de/strings.xml index 238b203..941e48e 100644 --- a/main/res/values-de/strings.xml +++ b/main/res/values-de/strings.xml @@ -814,7 +814,8 @@ <string name="trackable_released">Ausgesetzt</string> <string name="trackable_distance">Gereiste Strecke</string> <string name="trackable_touch">Trackable-Aktion</string> - + <string name="trackable_not_activated">Trackable nicht aktiviert</string> + <!-- user --> <string name="user_menu_title">Über</string> <string name="user_menu_view_hidden">Versteckte Caches</string> diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml index e82b44c..d2feffb 100644 --- a/main/res/values/strings.xml +++ b/main/res/values/strings.xml @@ -815,7 +815,8 @@ <string name="trackable_released">Released</string> <string name="trackable_distance">Travelled</string> <string name="trackable_touch">Touch</string> - + <string name="trackable_not_activated">Trackable not activated</string> + <!-- user --> <string name="user_menu_title">About</string> <string name="user_menu_view_hidden">Caches hidden</string> diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index bc9f934..c2a7b6d 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -7,11 +7,10 @@ import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.capability.ISearchByGeocode; -import cgeo.geocaching.connector.gc.GCConstants; +import cgeo.geocaching.connector.trackable.TrackableConnector; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; -import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.EditUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -61,7 +60,7 @@ public class SearchActivity extends AbstractActivity { super.onCreate(savedInstanceState); // search query - Intent intent = getIntent(); + final Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { final String query = intent.getStringExtra(SearchManager.QUERY); final boolean keywordSearch = intent.getBooleanExtra(Intents.EXTRA_KEYWORD_SEARCH, true); @@ -122,7 +121,7 @@ public class SearchActivity extends AbstractActivity { // otherwise see if this is a pure geocode if (StringUtils.isEmpty(geocode)) { - geocode = StringUtils.trim(query); + geocode = StringUtils.upperCase(StringUtils.trim(query)); } final IConnector connector = ConnectorFactory.getConnector(geocode); @@ -134,10 +133,10 @@ public class SearchActivity extends AbstractActivity { } // Check if the query is a TB code - final String trackable = TextUtils.getMatch(query, GCConstants.PATTERN_TB_CODE, true, 0, "", false); - if (StringUtils.isNotBlank(trackable)) { + final TrackableConnector trackableConnector = ConnectorFactory.getTrackableConnector(geocode); + if (trackableConnector != ConnectorFactory.UNKNOWN_TRACKABLE_CONNECTOR) { final Intent trackablesIntent = new Intent(this, TrackableActivity.class); - trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, trackable.toUpperCase(Locale.US)); + trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, geocode.toUpperCase(Locale.US)); startActivity(trackablesIntent); return true; } @@ -240,7 +239,7 @@ public class SearchActivity extends AbstractActivity { lonEdit.setHint(geo.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE_RAW)); } } - } catch (Exception e) { + } catch (final Exception e) { Log.w("Failed to update location."); } } @@ -250,7 +249,7 @@ public class SearchActivity extends AbstractActivity { @Override public void onClick(View arg0) { - CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); + final CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); coordsDialog.setCancelable(true); coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { @Override @@ -284,7 +283,7 @@ public class SearchActivity extends AbstractActivity { } else { try { cgeocaches.startActivityCoordinates(this, new Geopoint(StringUtils.trim(latText), StringUtils.trim(lonText))); - } catch (Geopoint.ParseException e) { + } catch (final Geopoint.ParseException e) { showToast(res.getString(e.resource)); } } @@ -300,7 +299,7 @@ public class SearchActivity extends AbstractActivity { private void findByKeywordFn() { // find caches by coordinates - String keyText = keywordEditText.getText().toString(); + final String keyText = keywordEditText.getText().toString(); if (StringUtils.isBlank(keyText)) { helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_keyword)); diff --git a/main/src/cgeo/geocaching/Trackable.java b/main/src/cgeo/geocaching/Trackable.java index f777351..d532cda 100644 --- a/main/src/cgeo/geocaching/Trackable.java +++ b/main/src/cgeo/geocaching/Trackable.java @@ -1,7 +1,8 @@ package cgeo.geocaching; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.trackable.TrackableConnector; import cgeo.geocaching.enumerations.LogType; -import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -38,17 +39,11 @@ public class Trackable implements ILogable { private String trackingcode = null; public String getUrl() { - if (StringUtils.startsWithIgnoreCase(getGeocode(), "GK")) { - String hex = getGeocode().substring(3); - try { - int id = Integer.parseInt(hex, 16); - return "http://geokrety.org/konkret.php?id=" + id; - } catch (NumberFormatException e) { - Log.e("Trackable.getUrl", e); - return null; - } - } - return "http://www.geocaching.com//track/details.aspx?tracker=" + geocode; + return getConnector().getUrl(this); + } + + private TrackableConnector getConnector() { + return ConnectorFactory.getConnector(this); } public String getGuid() { @@ -208,7 +203,7 @@ public class Trackable implements ILogable { } public boolean isLoggable() { - return !StringUtils.startsWithIgnoreCase(getGeocode(), "GK"); + return getConnector().isLoggable(); } public String getTrackingcode() { @@ -220,7 +215,7 @@ public class Trackable implements ILogable { } static public List<LogType> getPossibleLogTypes() { - List<LogType> logTypes = new ArrayList<LogType>(); + final List<LogType> logTypes = new ArrayList<LogType>(); logTypes.add(LogType.RETRIEVED_IT); logTypes.add(LogType.GRABBED_IT); logTypes.add(LogType.NOTE); diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 3319fe4..1849e4b 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -12,6 +12,10 @@ import cgeo.geocaching.connector.oc.OCApiConnector.ApiSupport; import cgeo.geocaching.connector.oc.OCApiLiveConnector; import cgeo.geocaching.connector.oc.OCConnector; import cgeo.geocaching.connector.ox.OXConnector; +import cgeo.geocaching.connector.trackable.GeokretyConnector; +import cgeo.geocaching.connector.trackable.TrackableConnector; +import cgeo.geocaching.connector.trackable.TravelBugConnector; +import cgeo.geocaching.connector.trackable.UnknownTrackableConnector; import cgeo.geocaching.geopoint.Viewport; import org.apache.commons.lang3.StringUtils; @@ -40,21 +44,28 @@ public final class ConnectorFactory { UNKNOWN_CONNECTOR // the unknown connector MUST be the last one }; + public static final UnknownTrackableConnector UNKNOWN_TRACKABLE_CONNECTOR = new UnknownTrackableConnector(); + private static final TrackableConnector[] TRACKABLE_CONNECTORS = new TrackableConnector[] { + new TravelBugConnector(), + new GeokretyConnector(), + UNKNOWN_TRACKABLE_CONNECTOR // must be last + }; + private static final ISearchByViewPort[] searchByViewPortConns; private static final ISearchByCenter[] searchByCenterConns; static { - List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); - for (IConnector conn : connectors) { + final List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); + for (final IConnector conn : connectors) { if (conn instanceof ISearchByViewPort) { vpConns.add((ISearchByViewPort) conn); } } searchByViewPortConns = vpConns.toArray(new ISearchByViewPort[vpConns.size()]); - List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); - for (IConnector conn : connectors) { + final List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); + for (final IConnector conn : connectors) { // GCConnector is handled specially, omit it here! if (conn instanceof ISearchByCenter && !(conn instanceof GCConnector)) { centerConns.add((ISearchByCenter) conn); @@ -75,7 +86,7 @@ public final class ConnectorFactory { if (isInvalidGeocode(geocode)) { return false; } - for (IConnector connector : connectors) { + for (final IConnector connector : connectors) { if (connector.canHandle(geocode)) { return true; } @@ -87,8 +98,17 @@ public final class ConnectorFactory { return getConnector(cache.getGeocode()); } - public static IConnector getConnector(Trackable trackable) { - return getConnector(trackable.getGeocode()); + public static TrackableConnector getConnector(Trackable trackable) { + return getTrackableConnector(trackable.getGeocode()); + } + + public static TrackableConnector getTrackableConnector(String geocode) { + for (final TrackableConnector connector : TRACKABLE_CONNECTORS) { + if (connector.canHandleTrackable(geocode)) { + return connector; + } + } + return UNKNOWN_TRACKABLE_CONNECTOR; // avoid null checks by returning a non implementing connector } public static IConnector getConnector(final String geocodeInput) { @@ -97,12 +117,12 @@ public final class ConnectorFactory { if (isInvalidGeocode(geocode)) { return UNKNOWN_CONNECTOR; } - for (IConnector connector : connectors) { + for (final IConnector connector : connectors) { if (connector.canHandle(geocode)) { return connector; } } - // in case of errors, take UNKNOWN + // in case of errors, take UNKNOWN to avoid null checks everywhere return UNKNOWN_CONNECTOR; } @@ -113,10 +133,10 @@ public final class ConnectorFactory { /** @see ISearchByViewPort#searchByViewport */ public static SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - SearchResult result = new SearchResult(); - for (ISearchByViewPort vpconn : searchByViewPortConns) { + final SearchResult result = new SearchResult(); + for (final ISearchByViewPort vpconn : searchByViewPortConns) { if (vpconn.isActivated()) { - SearchResult temp = vpconn.searchByViewport(viewport, tokens); + final SearchResult temp = vpconn.searchByViewport(viewport, tokens); if (temp != null) { result.addGeocodes(temp.getGeocodes()); } @@ -126,8 +146,8 @@ public final class ConnectorFactory { } public static String getGeocodeFromURL(final String url) { - for (IConnector connector : connectors) { - String geocode = connector.getGeocodeFromUrl(url); + for (final IConnector connector : connectors) { + final String geocode = connector.getGeocodeFromUrl(url); if (StringUtils.isNotBlank(geocode)) { return geocode; } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index d7fbbab..fab4332 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -35,6 +35,11 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, */ private static final Pattern gpxZipFilePattern = Pattern.compile("((\\d{7,})|(pocketquery))" + "(_.+)?" + "\\.zip", Pattern.CASE_INSENSITIVE); + /** + * Pattern for GC codes + */ + private final static Pattern PATTERN_GC_CODE = Pattern.compile("GC[0-9A-Z]+", Pattern.CASE_INSENSITIVE); + private GCConnector() { // singleton } @@ -55,7 +60,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, if (geocode == null) { return false; } - return GCConstants.PATTERN_GC_CODE.matcher(geocode).matches() || GCConstants.PATTERN_TB_CODE.matcher(geocode).matches(); + return GCConnector.PATTERN_GC_CODE.matcher(geocode).matches(); } @Override diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index b4f5845..f2e2e69 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -84,6 +84,7 @@ public final class GCConstants { public final static String ERROR_TB_DOES_NOT_EXIST = "does not exist in the system"; public final static String ERROR_TB_ELEMENT_EXCEPTION = "ElementNotFound Exception"; public final static String ERROR_TB_ARITHMETIC_OVERFLOW = "operation resulted in an overflow"; + public final static String ERROR_TB_NOT_ACTIVATED = "hasn't been activated"; /** * some parts of the webpage don't correctly encode the name, therefore this pattern must be checked with a * trackable name that needs HTML encoding @@ -157,12 +158,6 @@ public final class GCConstants { public final static Pattern PATTERN_VIEWSTATES = Pattern.compile("id=\"__VIEWSTATE(\\d*)\"[^(value)]+value=\"([^\"]+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_USERTOKEN = Pattern.compile("userToken\\s*=\\s*'([^']+)'"); - /** - * Patterns for GC and TB codes - */ - public final static Pattern PATTERN_GC_CODE = Pattern.compile("GC[0-9A-Z]+", Pattern.CASE_INSENSITIVE); - public final static Pattern PATTERN_TB_CODE = Pattern.compile("TB[0-9A-Z]+", Pattern.CASE_INSENSITIVE); - /** Live Map since 14.02.2012 */ public final static Pattern PATTERN_USERSESSION = Pattern.compile("UserSession\\('([^']+)'"); public final static Pattern PATTERN_SESSIONTOKEN = Pattern.compile("sessionToken:'([^']+)'"); @@ -194,7 +189,7 @@ public final class GCConstants { */ public static long gccodeToGCId(final String gccode) { long base = GC_BASE31; - String geocodeWO = gccode.substring(2).toUpperCase(Locale.US); + final String geocodeWO = gccode.substring(2).toUpperCase(Locale.US); if ((geocodeWO.length() < 4) || (geocodeWO.length() == 4 && SEQUENCE_GCID.indexOf(geocodeWO.charAt(0)) < 16)) { base = GC_BASE16; diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 7154b08..a58b2cc 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -28,10 +28,10 @@ import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.DirectionImage; -import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; @@ -79,7 +79,7 @@ public abstract class GCParser { // recaptcha String recaptchaChallenge = null; if (showCaptcha) { - String recaptchaJsParam = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); + final String recaptchaJsParam = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); if (recaptchaJsParam != null) { final Parameters params = new Parameters("k", recaptchaJsParam.trim()); @@ -109,7 +109,7 @@ public abstract class GCParser { page = page.substring(startPos); // cut on <table startPos = page.indexOf('>'); - int endPos = page.indexOf("ctl00_ContentBody_UnitTxt"); + final int endPos = page.indexOf("ctl00_ContentBody_UnitTxt"); if (startPos == -1 || endPos == -1) { Log.e("GCParser.parseSearch: ID \"ctl00_ContentBody_UnitTxt\" not found on page"); return null; @@ -122,7 +122,7 @@ public abstract class GCParser { for (int z = 1; z < rows_count; z++) { final Geocache cache = new Geocache(); - String row = rows[z]; + final String row = rows[z]; // check for cache type presence if (!row.contains("images/wpttypes")) { @@ -150,7 +150,7 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse GUID and/or Disabled Log.w("GCParser.parseSearch: Failed to parse GUID and/or Disabled data"); } @@ -203,7 +203,7 @@ public abstract class GCParser { if (matcherTbs.groupCount() > 0) { try { cache.setInventoryItems(Integer.parseInt(matcherTbs.group(1))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing trackables count", e); } inventoryPre = matcherTbs.group(2); @@ -241,7 +241,7 @@ public abstract class GCParser { if (null != result) { cache.setFavoritePoints(Integer.parseInt(result)); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.w("GCParser.parseSearch: Failed to parse favorite count"); } @@ -250,11 +250,11 @@ public abstract class GCParser { // total caches found try { - String result = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_TOTALCOUNT, false, 1, null, true); + final String result = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_TOTALCOUNT, false, 1, null, true); if (null != result) { searchResult.setTotal(Integer.parseInt(result)); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.w("GCParser.parseSearch: Failed to parse cache count"); } @@ -284,7 +284,7 @@ public abstract class GCParser { params.put("__VIEWSTATEFIELDCOUNT", String.valueOf(searchResult.viewstates.length)); } } - for (String cid : cids) { + for (final String cid : cids) { params.put("CID", cid); } @@ -308,7 +308,7 @@ public abstract class GCParser { LocParser.parseLoc(searchResult, coordinates); - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.parseSearch.CIDs", e); } } @@ -316,7 +316,7 @@ public abstract class GCParser { // get direction images if (Settings.getLoadDirImg()) { final Set<Geocache> caches = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); - for (Geocache cache : caches) { + for (final Geocache cache : caches) { if (cache.getCoords() == null && StringUtils.isNotEmpty(cache.getDirectionImg())) { DirectionImage.getDrawable(cache.getDirectionImg()); } @@ -327,7 +327,7 @@ public abstract class GCParser { } private static Float parseStars(final String value) { - float floatValue = Float.parseFloat(StringUtils.replaceChars(value, ',', '.')); + final float floatValue = Float.parseFloat(StringUtils.replaceChars(value, ',', '.')); return floatValue >= 0.5 && floatValue <= 5.0 ? floatValue : null; } @@ -408,7 +408,7 @@ public abstract class GCParser { String tableInside = page; - int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); + final int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); if (pos == -1) { Log.e("GCParser.parseCache: ID \"cacheDetails\" not found on page"); return null; @@ -422,7 +422,7 @@ public abstract class GCParser { if (result != null) { try { cache.setTerrain(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing terrain value", e); } } @@ -432,7 +432,7 @@ public abstract class GCParser { if (result != null) { try { cache.setDifficulty(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing difficulty value", e); } } @@ -453,7 +453,7 @@ public abstract class GCParser { cache.setHidden(Login.parseGcCustomDate(hiddenString)); } } - } catch (ParseException e) { + } catch (final ParseException e) { // failed to parse cache hidden date Log.w("GCParser.parseCache: Failed to parse cache hidden (event) date"); } @@ -461,7 +461,7 @@ public abstract class GCParser { // favorite try { cache.setFavoritePoints(Integer.parseInt(TextUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing favorite count", e); } @@ -478,7 +478,7 @@ public abstract class GCParser { if (StringUtils.isNotBlank(foundDateString)) { cache.setVisitedDate(Login.parseGcCustomDate(foundDateString).getTime()); } - } catch (ParseException e) { + } catch (final ParseException e) { // failed to parse cache found date Log.w("GCParser.parseCache: Failed to parse cache found date"); } @@ -495,7 +495,7 @@ public abstract class GCParser { try { cache.setCoords(new Geopoint(latlon)); cache.setReliableLatLon(true); - } catch (Geopoint.GeopointException e) { + } catch (final Geopoint.GeopointException e) { Log.w("GCParser.parseCache: Failed to parse cache coordinates", e); } } @@ -504,10 +504,10 @@ public abstract class GCParser { cache.setLocation(TextUtils.getMatch(page, GCConstants.PATTERN_LOCATION, true, "")); // cache hint - String result = TextUtils.getMatch(page, GCConstants.PATTERN_HINT, false, null); + final String result = TextUtils.getMatch(page, GCConstants.PATTERN_HINT, false, null); if (result != null) { // replace linebreak and paragraph tags - String hint = GCConstants.PATTERN_LINEBREAK.matcher(result).replaceAll("\n"); + final String hint = GCConstants.PATTERN_LINEBREAK.matcher(result).replaceAll("\n"); if (hint != null) { cache.setHint(StringUtils.replace(hint, "</p>", "").trim()); } @@ -539,8 +539,8 @@ public abstract class GCParser { // if the image name can be recognized, use the image name as attribute final String imageName = matcherAttributesInside.group(1).trim(); if (StringUtils.isNotEmpty(imageName)) { - int start = imageName.lastIndexOf('/'); - int end = imageName.lastIndexOf('.'); + final int start = imageName.lastIndexOf('/'); + final int end = imageName.lastIndexOf('.'); if (start >= 0 && end >= 0) { attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(Locale.US); } @@ -550,7 +550,7 @@ public abstract class GCParser { } cache.setAttributes(attributes); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache attributes Log.w("GCParser.parseCache: Failed to parse cache attributes"); } @@ -567,7 +567,7 @@ public abstract class GCParser { while (matcherSpoilersInside.find()) { // the original spoiler URL (include .../display/... contains a low-resolution image // if we shorten the URL we get the original-resolution image - String url = matcherSpoilersInside.group(1).replace("/display", ""); + final String url = matcherSpoilersInside.group(1).replace("/display", ""); String title = null; if (matcherSpoilersInside.group(3) != null) { @@ -579,7 +579,7 @@ public abstract class GCParser { } cache.addSpoiler(new Image(url, title, description)); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache spoilers Log.w("GCParser.parseCache: Failed to parse cache spoilers"); } @@ -613,7 +613,7 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache inventory Log.w("GCParser.parseCache: Failed to parse cache inventory (2)"); } @@ -625,8 +625,8 @@ public abstract class GCParser { final MatcherWrapper matcherLog = new MatcherWrapper(GCConstants.PATTERN_COUNTLOG, countlogs); while (matcherLog.find()) { - String typeStr = matcherLog.group(1); - String countStr = matcherLog.group(2).replaceAll("[.,]", ""); + final String typeStr = matcherLog.group(1); + final String countStr = matcherLog.group(2).replaceAll("[.,]", ""); if (StringUtils.isNotBlank(typeStr) && LogType.UNKNOWN != LogType.getByIconName(typeStr) @@ -635,7 +635,7 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache log count"); } @@ -653,7 +653,7 @@ public abstract class GCParser { cache.addOrChangeWaypoint(waypoint, false); cache.setUserModifiedCoords(true); } - } catch (Geopoint.GeopointException e) { + } catch (final Geopoint.GeopointException e) { } int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); @@ -785,7 +785,7 @@ public abstract class GCParser { // save to application search.setError(searchResult.getError()); search.setViewstates(searchResult.viewstates); - for (String geocode : searchResult.getGeocodes()) { + for (final String geocode : searchResult.getGeocodes()) { search.addGeocode(geocode); } return search; @@ -896,7 +896,7 @@ public abstract class GCParser { return null; } try { - JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); + final JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); if (response == null) { return null; } @@ -906,12 +906,12 @@ public abstract class GCParser { if (!response.has("data")) { return null; } - JSONObject data = response.getJSONObject("data"); + final JSONObject data = response.getJSONObject("data"); if (data == null) { return null; } return searchByCoords(new Geopoint(data.getDouble("lat"), data.getDouble("lng")), cacheType, showCaptcha, recaptchaReceiver); - } catch (JSONException e) { + } catch (final JSONException e) { Log.w("GCParser.searchByAddress", e); } @@ -1036,7 +1036,7 @@ public abstract class GCParser { if (trackables != null && !trackables.isEmpty()) { // we have some trackables to proceed final StringBuilder hdnSelected = new StringBuilder(); - for (TrackableLog tb : trackables) { + for (final TrackableLog tb : trackables) { final String action = Integer.toString(tb.id) + tb.action.action; final StringBuilder paramText = new StringBuilder("ctl00$ContentBody$LogBookPanel1$uxTrackables$repTravelBugs$ctl"); @@ -1057,7 +1057,7 @@ public abstract class GCParser { page = Network.getResponseData(Network.postRequest(uri, params)); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLog.confim", e); } @@ -1081,7 +1081,7 @@ public abstract class GCParser { return new ImmutablePair<StatusCode, String>(StatusCode.NO_ERROR, logID); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLog.check", e); } @@ -1131,7 +1131,7 @@ public abstract class GCParser { final File image = new File(imageUri.getPath()); final String response = Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$ImageUploadControl1$uxFileUpload", "image/jpeg", image)); - MatcherWrapper matcherUrl = new MatcherWrapper(GCConstants.PATTERN_IMAGE_UPLOAD_URL, response); + final MatcherWrapper matcherUrl = new MatcherWrapper(GCConstants.PATTERN_IMAGE_UPLOAD_URL, response); if (matcherUrl.find()) { Log.i("Logimage successfully uploaded."); @@ -1206,7 +1206,7 @@ public abstract class GCParser { Log.i("Log successfully posted to trackable #" + trackingCode); return StatusCode.NO_ERROR; } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLogTrackable.check", e); } @@ -1223,14 +1223,14 @@ public abstract class GCParser { */ static boolean addToWatchlist(final Geocache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?w=" + cache.getCacheId(); - String page = Login.postRequestLogged(uri, null); + final String page = Login.postRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("GCParser.addToWatchlist: No data from server"); return false; // error } - boolean guidOnPage = cache.isGuidContainedInPage(page); + final boolean guidOnPage = cache.isGuidContainedInPage(page); if (guidOnPage) { Log.i("GCParser.addToWatchlist: cache is on watchlist"); cache.setOnWatchlist(true); @@ -1264,7 +1264,7 @@ public abstract class GCParser { Login.transferViewstates(page, params); page = Network.getResponseData(Network.postRequest(uri, params)); - boolean guidOnPage = cache.isGuidContainedInPage(page); + final boolean guidOnPage = cache.isGuidContainedInPage(page); if (!guidOnPage) { Log.i("GCParser.removeFromWatchlist: cache removed from watchlist"); cache.setOnWatchlist(false); @@ -1309,7 +1309,7 @@ public abstract class GCParser { final String uri = "http://www.geocaching.com/datastore/favorites.svc/update?u=" + userToken + "&f=" + Boolean.toString(add); - HttpResponse response = Network.postRequest(uri, null); + final HttpResponse response = Network.postRequest(uri, null); if (response != null && response.getStatusLine().getStatusCode() == 200) { Log.i("GCParser.changeFavorite: cache added/removed to/from favorites"); @@ -1354,7 +1354,7 @@ public abstract class GCParser { final Trackable trackable = new Trackable(); // trackable geocode - trackable.setGeocode(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, trackable.getGeocode())); + trackable.setGeocode(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, StringUtils.upperCase(possibleTrackingcode))); // trackable id trackable.setGuid(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GUID, true, trackable.getGuid())); @@ -1377,7 +1377,7 @@ public abstract class GCParser { trackable.setOwnerGuid(matcherOwner.group(1)); trackable.setOwner(matcherOwner.group(2).trim()); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable owner name Log.w("GCParser.parseTrackable: Failed to parse trackable owner name"); } @@ -1408,21 +1408,21 @@ public abstract class GCParser { if (TextUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDOWNER)) { trackable.setSpottedType(Trackable.SPOTTED_OWNER); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable last known place Log.w("GCParser.parseTrackable: Failed to parse trackable last known place"); } // released date - can be missing on the page try { - String releaseString = TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_RELEASES, false, null); + final String releaseString = TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_RELEASES, false, null); if (releaseString != null) { trackable.setReleased(dateTbIn1.parse(releaseString)); if (trackable.getReleased() == null) { trackable.setReleased(dateTbIn2.parse(releaseString)); } } - } catch (ParseException e1) { + } catch (final ParseException e1) { trackable.setReleased(null); } @@ -1431,7 +1431,7 @@ public abstract class GCParser { if (null != distance) { try { trackable.setDistance(DistanceParser.parseDistance(distance, Settings.isUseMetricUnits())); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("GCParser.parseTrackable: Failed to parse distance", e); } } @@ -1453,10 +1453,13 @@ public abstract class GCParser { trackable.setDetails(convertLinks(details)); } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable details & image Log.w("GCParser.parseTrackable: Failed to parse trackable details & image"); } + if (StringUtils.isEmpty(trackable.getDetails()) && page.contains(GCConstants.ERROR_TB_NOT_ACTIVATED)) { + trackable.setDetails(cgeoapplication.getInstance().getString(R.string.trackable_not_activated)); + } // trackable logs try { @@ -1474,7 +1477,7 @@ public abstract class GCParser { long date = 0; try { date = Login.parseGcCustomDate(matcherLogs.group(2)).getTime(); - } catch (ParseException e) { + } catch (final ParseException e) { } final LogEntry logDone = new LogEntry( @@ -1502,7 +1505,7 @@ public abstract class GCParser { trackable.getLogs().add(logDone); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache logs", e); } @@ -1575,7 +1578,7 @@ public abstract class GCParser { rawResponse = TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""); } - List<LogEntry> logs = new ArrayList<LogEntry>(); + final List<LogEntry> logs = new ArrayList<LogEntry>(); try { final JSONObject resp = new JSONObject(rawResponse); @@ -1596,7 +1599,7 @@ public abstract class GCParser { long date = 0; try { date = Login.parseGcCustomDate(entry.getString("Visited")).getTime(); - } catch (ParseException e) { + } catch (final ParseException e) { Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); } @@ -1624,7 +1627,7 @@ public abstract class GCParser { logs.add(logDone); } - } catch (JSONException e) { + } catch (final JSONException e) { // failed to parse logs Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); } @@ -1641,16 +1644,16 @@ public abstract class GCParser { final MatcherWrapper typeBoxMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPEBOX, page); if (typeBoxMatcher.find() && typeBoxMatcher.groupCount() > 0) { - String typesText = typeBoxMatcher.group(1); + final String typesText = typeBoxMatcher.group(1); final MatcherWrapper typeMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPE2, typesText); while (typeMatcher.find()) { if (typeMatcher.groupCount() > 1) { try { - int type = Integer.parseInt(typeMatcher.group(2)); + final int type = Integer.parseInt(typeMatcher.group(2)); if (type > 0) { types.add(LogType.getById(type)); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing log types", e); } } @@ -1698,7 +1701,7 @@ public abstract class GCParser { Log.i("Trackable in inventory (#" + entry.ctl + "/" + entry.id + "): " + entry.trackCode + " - " + entry.name); trackableLogs.add(entry); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("GCParser.parseTrackableLog", e); } } @@ -1727,10 +1730,10 @@ public abstract class GCParser { //cache.setLogs(loadLogsFromDetails(page, cache, false)); if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - List<LogEntry> allLogs = cache.getLogs(); - List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); + final List<LogEntry> allLogs = cache.getLogs(); + final List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); if (friendLogs != null) { - for (LogEntry log : friendLogs) { + for (final LogEntry log : friendLogs) { if (allLogs.contains(log)) { allLogs.get(allLogs.indexOf(log)).friend = true; } else { @@ -1793,7 +1796,7 @@ public abstract class GCParser { final String uriSuffix = wpt != null ? "SetUserCoordinate" : "ResetUserCoordinate"; final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; - HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); if (response != null && response.getStatusLine().getStatusCode() == 200) { @@ -1801,7 +1804,7 @@ public abstract class GCParser { return true; } - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("Unknown exception with json wrap code", e); } Log.e("GCParser.deleteModifiedCoordinates - cannot delete modified coords"); @@ -1816,7 +1819,7 @@ public abstract class GCParser { } try { - JSONObject jo = new JSONObject() + final JSONObject jo = new JSONObject() .put("dto", (new JSONObject() .put("et", cache.getPersonalNote()) .put("ut", userToken))); @@ -1824,7 +1827,7 @@ public abstract class GCParser { final String uriSuffix = "SetUserCacheNote"; final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; - HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); if (response != null && response.getStatusLine().getStatusCode() == 200) { @@ -1832,7 +1835,7 @@ public abstract class GCParser { return true; } - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("Unknown exception with json wrap code", e); } Log.e("GCParser.uploadPersonalNote - cannot upload personal note"); diff --git a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java new file mode 100644 index 0000000..cd32d87 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java @@ -0,0 +1,10 @@ +package cgeo.geocaching.connector.trackable; + + +public abstract class AbstractTrackableConnector implements TrackableConnector { + + @Override + public boolean isLoggable() { + return false; + } +} diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java new file mode 100644 index 0000000..a351ebd --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java @@ -0,0 +1,32 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; +import cgeo.geocaching.utils.Log; + +import java.util.regex.Pattern; + +public class GeokretyConnector extends AbstractTrackableConnector { + + private static final Pattern PATTERN_GK_CODE = Pattern.compile("GK[0-9A-F]{4,}"); + + @Override + public boolean canHandleTrackable(String geocode) { + return geocode != null && PATTERN_GK_CODE.matcher(geocode).matches(); + } + + @Override + public String getUrl(Trackable trackable) { + return "http://geokrety.org/konkret.php?id=" + getId(trackable); + } + + private static int getId(Trackable trackable) { + try { + final String hex = trackable.getGeocode().substring(2); + return Integer.parseInt(hex, 16); + } catch (final NumberFormatException e) { + Log.e("Trackable.getUrl", e); + } + return -1; + } + +} diff --git a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java new file mode 100644 index 0000000..7df8560 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java @@ -0,0 +1,17 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +/** + * Methods to be implemented by any connector for handling trackables + * + */ +public interface TrackableConnector { + + public boolean canHandleTrackable(final String geocode); + + public String getUrl(final Trackable trackable); + + public boolean isLoggable(); + +} diff --git a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java new file mode 100644 index 0000000..74e0ec5 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java @@ -0,0 +1,28 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +import java.util.regex.Pattern; + +public class TravelBugConnector extends AbstractTrackableConnector { + + /** + * TB codes really start with TB1, there is no padding or minimum length + */ + private final static Pattern PATTERN_TB_CODE = Pattern.compile("TB[0-9A-Z]+", Pattern.CASE_INSENSITIVE); + + @Override + public boolean canHandleTrackable(String geocode) { + return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches(); + } + + @Override + public String getUrl(Trackable trackable) { + return "http://www.geocaching.com//track/details.aspx?tracker=" + trackable.getGeocode(); + } + + @Override + public boolean isLoggable() { + return true; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java new file mode 100644 index 0000000..17fa680 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java @@ -0,0 +1,19 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +import org.apache.commons.lang3.StringUtils; + +public class UnknownTrackableConnector extends AbstractTrackableConnector { + + @Override + public boolean canHandleTrackable(String geocode) { + return false; + } + + @Override + public String getUrl(Trackable trackable) { + return StringUtils.EMPTY; + } + +} diff --git a/tests/res/raw/tb123e_html.html b/tests/res/raw/tb123e_html.html new file mode 100644 index 0000000..3c933cc --- /dev/null +++ b/tests/res/raw/tb123e_html.html @@ -0,0 +1,877 @@ +
+
+<!DOCTYPE html>
+<html lang="en" class="no-js">
+<head id="ctl00_Head1"><meta charset="utf-8" />
+ <!--[if IE]><![endif]-->
+ <title>
+ Geocaching > Trackable Items > Trackable Item Details
+</title><meta name="DC.title" content="Geocaching - The Official Global GPS Cache Hunt Site" /><meta name="author" content="Groundspeak, Inc." /><meta name="DC.creator" content="Groundspeak, Inc." /><meta name="Copyright" content="Copyright (c) 2000-2013 Groundspeak, Inc. All Rights Reserved." /><!-- Copyright (c) 2000-2013 Groundspeak, Inc. All Rights Reserved. --><meta name="description" content="Geocaching is a treasure hunting game where you use a GPS to hide and seek containers with other participants in the activity. Geocaching.com is the listing service for geocaches around the world." /><meta name="DC.subject" content="Geocaching is a treasure hunting game where you use a GPS to hide and seek containers with other participants in the activity. Geocaching.com is the listing service for geocaches around the world." /><meta http-equiv="imagetoolbar" content="no" /><meta name="distribution" content="global" /><meta name="MSSmartTagsPreventParsing" content="true" /><meta name="rating" content="general" /><meta name="revisit-after" content="1 days" /><meta name="robots" content="all" /><link rel="icon" href="/favicon.ico" /><link rel="shortcut icon" href="/favicon.ico" /><link rel="apple-touch-icon" href="/apple-touch-icon.png" /><link rel="stylesheet" type="text/css" media="all" href="../css/blueprint/src/reset.css" /><link rel="stylesheet" type="text/css" media="all" href="../css/blueprint/src/typography.css" /><link rel="stylesheet" type="text/css" media="screen,projection" href="../css/blueprint/src/grid.css" />
+ <!--[if lt IE 8]>
+ <link rel="stylesheet" type="text/css" media="all" href="../css/blueprint/ie.css" />
+ <![endif]-->
+ <link id="uxCssMaster" rel="stylesheet" type="text/css" media="screen,projection" href="../css/tlnMasterScreen.css?r=1" /><link id="uxCssMain" rel="stylesheet" type="text/css" media="all" href="../css/tlnMain.css?r=1" /><link rel="Stylesheet" type="text/css" media="all" href="../css/jqueryui1810/jquery-ui-1.8.10.custom.css" /><link rel="stylesheet" type="text/css" media="all" href="/js/jquery_plugins/jquery.jgrowl.css" /><link rel="stylesheet" type="text/css" media="print" href="../css/tlnMasterPrint.css" />
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ </script>
+ <script type="text/javascript" src="/js/modernizr-1.7.min.js"></script>
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
+
+ <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js" type="text/javascript"></script>
+ <script type="text/javascript" src="/js/jquery.truncate.min.js"></script>
+
+ <script type='text/javascript'>
+ var googletag = googletag || {};
+ googletag.cmd = googletag.cmd || [];
+ (function () {
+ var gads = document.createElement('script');
+ gads.async = true;
+ gads.type = 'text/javascript';
+ var useSSL = 'https:' == document.location.protocol;
+ gads.src = (useSSL ? 'https:' : 'http:') + '//www.googletagservices.com/tag/js/gpt.js';
+ var node = document.getElementsByTagName('script')[0];
+ node.parentNode.insertBefore(gads, node);
+ })();
+ </script>
+
+
+ <link href="/css/fancybox/jquery.fancybox.css" rel="stylesheet" type="text/css" />
+ <style type="text/css" media="screen">
+ .MasterPageAds{
+ margin-top:0;
+ }
+ .CoordInfoLinkWidget{
+ margin-right:-175px;
+ }
+ ul.imagelist li, ul.log_images li
+ {
+ list-style: none;
+ }
+ ul.log_images li
+ {
+ float: left;
+ margin-right: 5px;
+ }
+ ul.pager
+ {
+ padding: 0 !important;
+ margin: 5px 0 !important;
+ width: 100%;
+ overflow: hidden;
+ }
+ ul.pager li
+ {
+ float: left;
+ list-style: none;
+ }
+ ul.pager li.pager-info
+ {
+ margin: 2px 5px 0 0 !important;
+ }
+ ul.pager li.pager-current
+ {
+ border: 1px solid #003F7E;
+ color: #c00;
+ font-weight: bold;
+ margin: 0 5px 0 0;
+ padding: 1px 5px;
+ }
+ ul.pager li a
+ {
+ border: 1px solid #ccc;
+ display: block;
+ margin: 0 5px 0 0;
+ padding: 1px 5px;
+ text-decoration: none;
+ }
+ ul.pager li a:hover
+ {
+ border-color: #003F7E;
+ }
+ .LogImgTitle, .LogImgDescription
+ {
+ text-align: center !important;
+ width: 100%;
+ }
+ .TrackableItemOptionsTable
+ {
+ clear:both;
+ }
+ </style>
+<meta name="og:site_name" content="Geocaching.com" property="og:site_name" /><meta name="og:type" content="article" property="og:type" /><meta name="fb:app_id" content="251051881589204" property="fb:app_id" /><meta name="og:url" content="http://www.geocaching.com/track/details.aspx?tracker=TB123E" property="og:url" /><meta name="og:description" property="og:description" /><meta name="og:image" content="http://www.geocaching.com/images/facebook/wpttypes/14.png" property="og:image" /><meta name="og:title" content="Travel Bug Dog Tag" property="og:title" /></head>
+<body >
+ <form name="aspnetForm" method="post" action="details.aspx?tracker=TB123E" onsubmit="javascript:return WebForm_OnSubmit();" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'ctl00_btnSignIn')" id="aspnetForm">
+<div>
+<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
+<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
+<input type="hidden" name="__VIEWSTATEFIELDCOUNT" id="__VIEWSTATEFIELDCOUNT" value="2" />
+<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTM3NDI4MjQ0OA8WAh4MVEJEZXRhaWxzLklEKClZU3lzdGVtLkludDY0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkENDY3MBYCZg9kFgZmD2QWCgIGDxYCHgRUZXh0BWI8bWV0YSBuYW1lPSJDb3B5cmlnaHQiIGNvbnRlbnQ9IkNvcHlyaWdodCAoYykgMjAwMC0yMDEzIEdyb3VuZHNwZWFrLCBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuIiAvPmQCBw8WAh8BBUc8IS0tIENvcHlyaWdodCAoYykgMjAwMC0yMDEzIEdyb3VuZHNwZWFrLCBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuIC0tPmQCGQ8WAh4EaHJlZgUdfi9jc3MvdGxuTWFzdGVyU2NyZWVuLmNzcz9yPTFkAhoPFgIfAgUVfi9jc3MvdGxuTWFpbi5jc3M/cj0xZAIfDxYCHgdWaXNpYmxlaGQCAQ9kFgwCCw8WAh8BZGQCDQ9kFgoCAw8WAh8DZxYCAh0PDxYCHgtQb3N0QmFja1VybAVeaHR0cHM6Ly93d3cuZ2VvY2FjaGluZy5jb20vbG9naW4vZGVmYXVsdC5hc3B4P3JlZGlyPSUyZnRyYWNrJTJmZGV0YWlscy5hc3B4JTNmdHJhY2tlciUzZFRCMTIzRWRkAgUPZBYCAgUPDxYCHgtOYXZpZ2F0ZVVybAWFAWh0dHBzOi8vd3d3Lmdlb2NhY2hpbmcuY29tL2xvZ2luL2RlZmF1bHQuYXNweD9SRVNFVD1ZJnJlZGlyPWh0dHAlM2ElMmYlMmZ3d3cuZ2VvY2FjaGluZy5jb20lMmZ0cmFjayUyZmRldGFpbHMuYXNweCUzZnRyYWNrZXIlM2RUQjEyM0VkZAINDxYCHwNnZAIfDxYCHwNnZAJLD2QWBAIDDxYCHwEFB0VuZ2xpc2hkAgUPFgIeC18hSXRlbUNvdW50AhMWJmYPZBYCAgEPDxYIHg9Db21tYW5kQXJndW1lbnQFBWVuLVVTHgtDb21tYW5kTmFtZQUNU2V0VGVtcExvY2FsZR8BBQdFbmdsaXNoHhBDYXVzZXNWYWxpZGF0aW9uaGRkAgEPZBYCAgEPDxYIHwcFBWRlLURFHwgFDVNldFRlbXBMb2NhbGUfAQUHRGV1dHNjaB8JaGRkAgIPZBYCAgEPDxYIHwcFBWZyLUZSHwgFDVNldFRlbXBMb2NhbGUfAQUJRnJhbsOnYWlzHwloZGQCAw9kFgICAQ8PFggfBwUFcHQtUFQfCAUNU2V0VGVtcExvY2FsZR8BBQpQb3J0dWd1w6pzHwloZGQCBA9kFgICAQ8PFggfBwUFY3MtQ1ofCAUNU2V0VGVtcExvY2FsZR8BBQnEjGXFoXRpbmEfCWhkZAIFD2QWAgIBDw8WCB8HBQVzdi1TRR8IBQ1TZXRUZW1wTG9jYWxlHwEFB1N2ZW5za2EfCWhkZAIGD2QWAgIBDw8WCB8HBQVlcy1FUx8IBQ1TZXRUZW1wTG9jYWxlHwEFCEVzcGHDsW9sHwloZGQCBw9kFgICAQ8PFggfBwUFZXQtRUUfCAUNU2V0VGVtcExvY2FsZR8BBQVFZXN0aR8JaGRkAggPZBYCAgEPDxYIHwcFBWl0LUlUHwgFDVNldFRlbXBMb2NhbGUfAQUISXRhbGlhbm8fCWhkZAIJD2QWAgIBDw8WCB8HBQVlbC1HUh8IBQ1TZXRUZW1wTG9jYWxlHwEFEM6VzrvOu863zr3Ouc66zqwfCWhkZAIKD2QWAgIBDw8WCB8HBQVsdi1MVh8IBQ1TZXRUZW1wTG9jYWxlHwEFCUxhdHZpZcWhdR8JaGRkAgsPZBYCAgEPDxYIHwcFBW5sLU5MHwgFDVNldFRlbXBMb2NhbGUfAQUKTmVkZXJsYW5kcx8JaGRkAgwPZBYCAgEPDxYIHwcFBWNhLUVTHwgFDVNldFRlbXBMb2NhbGUfAQUHQ2F0YWzDoB8JaGRkAg0PZBYCAgEPDxYIHwcFBXBsLVBMHwgFDVNldFRlbXBMb2NhbGUfAQUGUG9sc2tpHwloZGQCDg9kFgICAQ8PFggfBwUFbmItTk8fCAUNU2V0VGVtcExvY2FsZR8BBQ5Ob3JzaywgQm9rbcOlbB8JaGRkAg8PZBYCAgEPDxYIHwcFBWtvLUtSHwgFDVNldFRlbXBMb2NhbGUfAQUJ7ZWc6rWt7Ja0HwloZGQCEA9kFgICAQ8PFggfBwUFaHUtSFUfCAUNU2V0VGVtcExvY2FsZR8BBQZNYWd5YXIfCWhkZAIRD2QWAgIBDw8WCB8HBQVyby1STx8IBQ1TZXRUZW1wTG9jYWxlHwEFCFJvbcOibsSDHwloZGQCEg9kFgICAQ8PFggfBwUFamEtSlAfCAUNU2V0VGVtcExvY2FsZR8BBQnml6XmnKzoqp4fCWhkZAITDxYCHgVjbGFzcwUHc3Bhbi0yMBYCAgEPZBYQAgMPZBYCZg9kFgICAQ8PFgIfAQUGVEIxMjNFZGQCBQ8PFgQeCEltYWdlVXJsBTBodHRwOi8vd3d3Lmdlb2NhY2hpbmcuY29tL2ltYWdlcy93cHR0eXBlcy8yMS5naWYeDUFsdGVybmF0ZVRleHQFElRyYXZlbCBCdWcgRG9nIFRhZ2RkAgcPDxYCHwEFElRyYXZlbCBCdWcgRG9nIFRhZ2RkAgkPDxYEHwEF6gE8cCBjbGFzcz0iV2FybmluZyI+PGltZyBzcmM9Ii9pbWFnZXMvaWNvbnMvMTYvdHJhY2thYmxlX2Vycm9yLnBuZyIgYWx0PSdBdHRlbnRpb24hJyAvPlRoaXMgVHJhY2thYmxlICg8c3Ryb25nPlRyYXZlbCBCdWcgRG9nIFRhZzwvc3Ryb25nPikgaGFzbid0IGJlZW4gYWN0aXZhdGVkLiA8YSBocmVmPSJhY3RpdmF0ZS5hc3B4IiB0aXRsZT0iQWN0aXZhdGUgSXQgTm93Ij5BY3RpdmF0ZSBpdCBub3c8L2E+LjwvcD4fA2dkZAINDw8WAh8DaGQWFmYPZBYCZg8PFgIfAQUiPHN0cm9uZz5UcmFja2FibGUgT3B0aW9uczwvc3Ryb25nPmRkAgEPZBYCZg9kFgJmDw8WBB8FBWtodHRwczovL3d3dy5nZW9jYWNoaW5nLmNvbS9sb2dpbi8/UkVTRVQ9WSZyZWRpcj1odHRwOi8vd3d3Lmdlb2NhY2hpbmcuY29tL3RyYWNrL2RldGFpbHMuYXNweD90cmFja2VyPVRCMTIzRR8BBRhGb3VuZCB0aGlzIGl0ZW0/IExvZyBpbi5kZAICDw8WAh8DaGRkAgMPDxYCHwNoZGQCBA8PFgIfA2hkZAIFDw8WAh8DaGRkAgYPZBYCZg9kFgJmDw8WAh8FBTx+L3RyYWNrL3NoZWV0LmFzcHg/Z3VpZD1hNWM3YmYwMS1iMDI4LTRhMDAtODIzMS1lNDIwZjZiZDI2ZTdkZAIHDw8WAh8DaGRkAggPDxYCHwNoZGQCDA8PFgIfA2hkZAINDw8WAh8DaGQWAmYPZBYCZg8QZGQWAGQCDw8PFgQeC0J1Z1BhbmVsLklEKCsEBDQ2NzAfA2hkZAITDw8WAh8DaGRkAhUPDxYCHwNnZGQCFQ8WAh8KBQtzcGFuLTQgbGFzdBYCAgEPD2QWAh4Fc3R5bGUFDHdpZHRoOjE2MHB4O2QCFw9kFgQCAw8WAh8BBQdFbmdsaXNoZAIFDxYCHwYCExYmZg9kFgICAQ8PFggfBwUFZW4tVVMfCAUNU2V0VGVtcExvY2FsZR8BBQdFbmdsaXNoHwloZGQCAQ9kFgICAQ8PFggfBwUFZGUtREUfCAUNU2V0VGVtcExvY2FsZR8BBQdEZXV0c2NoHwloZGQCAg9kFgICAQ8PFggfBwUFZnItRlIfCAUNU2V0VGVtcExvY2FsZR8BBQlGcmFuw6dhaXMfCWhkZAIDD2QWAgIBDw8WCB8HBQVwdC1QVB8IBQ1TZXRUZW1wTG9jYWxlHwEFClBvcnR1Z3XDqnMfCWhkZAIED2QWAgIBDw8WCB8HBQVjcy1DWh8IBQ1TZXRUZW1wTG9jYWxlHwEFCcSMZcWhdGluYR8JaGRkAgUPZBYCAgEPDxYIHwcFBXN2LVNFHwgFDVNldFRlbXBMb2NhbGUfAQUHU3ZlbnNrYR8JaGRkAgYPZBYCAgEPDxYIHwcFBWVzLUVTHwgFDVNldFRlbXBMb2NhbGUfAQUIRXNwYcOxb2wfCWhkZAIHD2QWAgIBDw8WCB8HBQVldC1FRR8IBQ1TZXRUZW1wTG9jYWxlHwEFBUVlc3RpHwloZGQCCA9kFgICAQ8PFggfBwUFaXQtSVQfCAUNU2V0VGVtcExvY2FsZR8BBQhJdGFsaWFubx8JaGRkAgkPZBYCAgEPDxYIHwcFBWVsLUdSHwgFDVNldFRlbXBMb2NhbGUfAQUQzpXOu867zrfOvc65zrrOrB8JaGRkAgoPZBYCAgEPDxYIHwcFBWx2LUxWHwgFDVNldFRlbXBMb2NhbGUfAQUJTGF0dmllxaF1HwloZGQCCw9kFgICAQ8PFggfBwUFbmwtTkwfCAUNU2V0VGVtcExvY2FsZR8BBQpOZWRlcmxhbmRzHwloZGQCDA9kFgICAQ8PFggfBwUFY2EtRVMfCAUNU2V0VGVtcExvY2FsZR8BBQdDYXRhbMOgHwloZGQCDQ9kFgICAQ8PFggfBwUFcGwtUEwfCAUNU2V0VGVtcExvY2FsZR8BBQZQb2xza2kfCWhkZAIOD2QWAgIBDw8WCB8HBQVuYi1OTx8IBQ1TZXRUZW1wTG9jYWxlHwEFDk5vcnNrLCBCb2ttw6VsHwloZGQCDw9kFgICAQ8PFggfBwUFa28tS1IfCAUNU2V0VGVtcExvY2FsZR8BBQntlZzqta3slrQfCWhkZAIQD2QWAgIBDw8WCB8HBQVodS1IVR8IBQ1TZXRUZW1wTG9jYWxlHwEFBk1hZ3lhch8JaGRkAhEPZBYCAgEPDxYIHwcF" />
+<input type="hidden" name="__VIEWSTATE1" id="__VIEWSTATE1" value="BXJvLVJPHwgFDVNldFRlbXBMb2NhbGUfAQUIUm9tw6JuxIMfCWhkZAISD2QWAgIBDw8WCB8HBQVqYS1KUB8IBQ1TZXRUZW1wTG9jYWxlHwEFCeaXpeacrOiqnh8JaGRkAksPFgIfAQUQJmNvcHk7IDIwMDAtMjAxM2QCAw8WAh8BBStTZXJ2ZXI6IFdFQjE1OyBCdWlsZDogV2ViLkhvdEZpeF8yMDEzMDYxMS4xZBgBBR5fX0NvbnRyb2xzUmVxdWlyZVBvc3RCYWNrS2V5X18WAQUSY3RsMDAkY2JSZW1lbWJlck1lPdC9wWLlFkz2FeUukOGHwoH4Qbo=" />
+</div>
+
+<script type="text/javascript">
+//<![CDATA[
+var theForm = document.forms['aspnetForm'];
+if (!theForm) {
+ theForm = document.aspnetForm;
+}
+function __doPostBack(eventTarget, eventArgument) {
+ if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
+ theForm.__EVENTTARGET.value = eventTarget;
+ theForm.__EVENTARGUMENT.value = eventArgument;
+ theForm.submit();
+ }
+}
+//]]>
+</script>
+
+
+<script src="/WebResource.axd?d=Dh2VENdI9XyWNN0f7DnYfR8WWRCRIzdVqal2y0yjiQ5nC_eHhLchYgnQDHIk0d3RCcSUMVZ36ciRD0qmhXKmeu3S_RE1&t=634981136634411315" type="text/javascript"></script>
+
+
+<script src="/ScriptResource.axd?d=8q09cuy9-PZDS8eZx6HLVXd3YZywgUmuoy8VVtGr3vaISS8ybaBLyQLFlq93nr2dlYP0QJWcJvooQKkloTdALTrS571k1UcPpD2pbXbpthIOf2Mn8gCnpyvfv-ZudWTMi0y39YZC0-Q0yjjxQ82yTlKdhBw1&t=150492e7" type="text/javascript"></script>
+<script src="/ScriptResource.axd?d=I9_m2Hb1Tv_B0qTMDG8bMbnkNSHUkv5oUaG9-V5NZ8qQ2VFlu60I8y8gfr3vPmZjbiPnu43MOQdFVDeYF-nDAEKBLmyxD3DCTGmes9NNbbvaDEHyEuuRWgccIkK3ik5TI48YGDxjHjqdn-gTK4Fkgd17LGw1&t=ffffffff940d030f" type="text/javascript"></script>
+<script src="/ScriptResource.axd?d=8vNbe34dAujgZMPnfnacfjeoweX1vHgyns8KlAV4vpGpsZC9Cf3pro__lv8ekBa0NiCgXGMMolzOUNH__lrnEI_qjlNBIAuuLeemtAXV_i6E0QIMZa8nGSYmWGF5nQOJK3rmZzvTxsr2Mh4Ebdba_1ywGLUSH_U_XIe-jzecfRQwwvjZ0&t=ffffffff940d030f" type="text/javascript"></script>
+<script src="/ScriptResource.axd?d=GOLGf77ZX6urEYf5Cg_l0ie9kWEGMu6XJMB7V1W0y_T0ZHAQx1nqrvH4nS3Bd1UlB4KJ0-apCBVwzxwWFhcyWq6Mb4W_l5ChkNoKVRbRgSdsqHaINGCvfTh-Xlc6A43jbSbHZPqZXuRx6l77VNJliDwnzeqaR6rOGx-bITJ5Ucpg2LG4c-dv5WgQ5ubixToUFzLOhih1vbqpBrUsntSxk1Ja38c1" type="text/javascript"></script>
+<script src="/ScriptResource.axd?d=D73YDIzBf7Ie7aEM8go5MHSjFGcfmjQmvpic2heYHDtQYqmKwdt34dMz4K3Ih4PuqodTDp2lDypwsCMnDfViOhnOocylUNAEdhl6X9_ny5t8XhdMnQzNj9zaOXspXEC5a1PHTg2" type="text/javascript"></script>
+<script src="/WebResource.axd?d=Np8DmZmh2tcIkgE9SU7mE6n1BWbJ0opng-rHikqBjFQo0nbLYSeBBVhJhCHcmx1ihKnAnQv_ZhnItUUjENzIzc1uoXk1&t=634981136634411315" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+function WebForm_OnSubmit() {
+if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false;
+return true;
+}
+//]]>
+</script>
+
+<div>
+
+ <input type="hidden" name="__PREVIOUSPAGE" id="__PREVIOUSPAGE" value="oJ2vMc_wjAUsI8uitLTiwYMiz9Z-iPQ0NBSttGTzhOx1wp3B2PVYaxCkmibTiuq2TKWnEfVrZoFL1sFtgd1KsT9I3Fg1" />
+</div>
+ <script type="text/javascript">
+//<![CDATA[
+Sys.WebForms.PageRequestManager._initialize('ctl00$uxMainScriptManager', 'aspnetForm', [], [], [], 90, 'ctl00');
+//]]>
+</script>
+
+ <div id="Top" class="SkipLinks">
+ <a id="ctl00_hlSkipLinksNavigation" accesskey="n" title="Skip to Navigation" href="#Navigation">Skip to Navigation</a>
+ <a id="ctl00_hlSkipLinksContent" accesskey="c" title="Skip to Content" href="#Content">Skip to Content</a>
+ </div>
+ <!--[if lte IE 7]>
+ <div class="WarningMessage PhaseOut">
+ <p>Groundspeak is phasing out support for older browsers. Visit the <a href="http://support.groundspeak.com/index.php?pg=kb.page&id=215" title="Browser Support Information">Help Center</a> for more information.</p>
+ </div>
+ <![endif]-->
+
+
+ <div class="PrintOnly">
+ <p>
+ <img src="/images/logo_print_bw.png" alt="Geocaching.com" />
+ </p>
+ <hr />
+ </div>
+ <header id="ctl00_siteHeader">
+ <div class="container">
+ <h1 class="Logo span-16">
+ <a href="../" id="ctl00_HDHomeLink" title="Geocaching" accesskey="h">Geocaching</a>
+ </h1>
+ <div class="ProfileWidget span-8 last">
+ <div id="ctl00_divNotSignedIn" class="FloatContainer">
+ <p class="NotSignedInText"><strong>
+ Welcome, Visitor!</strong><br />
+ </p>
+ <p class="LoginWithFacebook">
+ <a id="ctl00_uxSignIn" title="Login with Facebook" class="btnFacebookLogin NoWrap" href="javascript:__doPostBack('ctl00$uxSignIn','')"><span>
+ Login with Facebook</span></a>
+ </p>
+ <p class="NotSignedInLinks clear">
+ <a id="hlSignIn" accesskey="s" title="Sign In" class="SignInLink" href="/login/">Sign In</a> | <a id="ctl00_hlRegister" accesskey="r" title="Sign Up" href="../membership/register.aspx?type=basic">Sign Up</a>
+ </p>
+ <div id="SignInWidget">
+ <h3>
+ Sign In with Geocaching</h3>
+ <div id="ctl00_vsSignInWidgetForm" class="FormSummaryWidget FormErrorWidget InputWidth" style="color:Red;display:none;">
+
+</div>
+ <p>
+ <label for="ctl00_tbUsername" id="ctl00_lblUsername" title="Geocaching Username">Geocaching Username:</label><br />
+ <input name="ctl00$tbUsername" type="text" id="ctl00_tbUsername" class="text" autocomplete="off" />
+ <span id="ctl00_rfvUsername" title="Username is a required field; please enter a valid username." style="color:Red;display:none;"><span class="FormIcon FormValidationIcon">*</span></span>
+ </p>
+ <p>
+ <label for="ctl00_tbPassword" id="ctl00_lblPassword" title="Geocaching Password">Geocaching Password:</label><br />
+ <input name="ctl00$tbPassword" type="password" id="ctl00_tbPassword" class="text" autocomplete="off" />
+ <span id="ctl00_rfvPassword" title="Password is a required field; please enter a valid password." style="color:Red;display:none;"><span class="FormIcon FormValidationIcon">*</span></span>
+ </p>
+ <p>
+ <span class="Checkbox" title="Keep Me Signed In" autocomplete="off"><input id="ctl00_cbRememberMe" type="checkbox" name="ctl00$cbRememberMe" /><label for="ctl00_cbRememberMe">Keep Me Signed In</label></span><br />
+ <small>
+ Uncheck if on a shared computer.</small>
+ </p>
+ <p>
+ <input type="submit" name="ctl00$btnSignIn" value="Sign In" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$btnSignIn", "", true, "SignInForm", "https://www.geocaching.com/login/default.aspx?redir=%2ftrack%2fdetails.aspx%3ftracker%3dTB123E", false, false))" id="ctl00_btnSignIn" title="Sign In" />
+ or
+ <a id="ctl00_hlSignInClose" title="Close this Window" class="SignInCloseLink" href="javascript:void(0);">Close</a>
+ </p>
+ <p>
+ <small>
+ <a id="ctl00_hlForgot" title="Forgot your username or password?" href="../login/password.aspx">Forgot your username or password?</a></small>
+ </p>
+ </div>
+ </div>
+
+ </div>
+ <div class="NavContainer span-24 last">
+ <nav id="Navigation">
+ <ul class="Menu">
+ <li>
+ <a id="ctl00_hlNavLearn" accesskey="1" title="Learn" href="../guide/">Learn ▼</a>
+ <ul class="SubMenu">
+ <li>
+ <a id="ctl00_hlSubNavGeocaching101" accesskey="i" title="Geocaching 101" href="../guide/">Geocaching 101</a></li>
+ <li>
+ <a id="ctl00_hlSubNavGeocaching2Minutes" title="Geocaching in 2 Minutes" href="../videos/#cat=cat:newbies&vid=-4VFeYZTTYs">Geocaching in 2 Minutes</a></li>
+ </ul>
+ </li>
+ <li id="ctl00_liNavJoin">
+ <a id="ctl00_hlNavJoin" accesskey="2" title="Join" href="../membership/register.aspx?type=basic">Join</a></li>
+
+ <li>
+ <a id="ctl00_hlNavPlay" accesskey="3" title="Play" href="../seek/">Play ▼</a>
+ <ul class="SubMenu">
+ <li>
+ <a id="ctl00_hlSubNavHide" accesskey="d" title="Hide & Seek a Cache" href="../seek/">Hide & Seek a Cache</a></li>
+ <li>
+ </li>
+ <li>
+ <a id="ctl00_hlSubNavMap" accesskey="/" title="View Geocache Map" href="../map/">View Geocache Map</a></li>
+ <li>
+ <a id="ctl00_hlSubNavTrackables" accesskey="e" title="Find Trackables" href="./">Find Trackables</a></li>
+ <li>
+ <a id="ctl00_hlSubNavHelpCenter" title="Help Center" rel="external" href="http://support.groundspeak.com/index.php">Help Center</a></li>
+ </ul>
+ </li>
+ <li>
+ <a id="ctl00_hlNavCommunity" accesskey="6" title="Community" href="../forums/">Community ▼</a>
+ <ul class="SubMenu">
+
+ <li>
+ <a id="ctl00_hlSubNavTellaFriend" accesskey="-" title="Tell a Friend" href="../account/SendReferral.aspx">Tell a Friend</a>
+ </li>
+
+ <li>
+ <a id="ctl00_hlSubNavVolunteers" accesskey="+" title="Volunteers" href="../volunteers/">Volunteers</a></li>
+ <li>
+ <a id="ctl00_hlSubNavLocal" accesskey="z" title="Local Organizations" href="../organizations/">Local Organizations</a></li>
+ <li>
+ <a id="ctl00_hlSubNavDiscussionForums" accesskey="f" title="Discussion Forums" href="../forums/">Discussion Forums</a></li>
+ <li>
+ <a id="ctl00_hlSubNavBlog" accesskey="b" title="Blog" rel="external" href="http://blog.geocaching.com/">Blog</a></li>
+ <li>
+ <a id="ctl00_hlSubNavEvents" accesskey="v" title="Events" href="../calendar/">Events</a></li>
+ </ul>
+ </li>
+ <li>
+ <a id="ctl00_hlNavShop" accesskey="4" title="Shop" href="http://shop.geocaching.com/">Shop ▼</a>
+ <ul class="SubMenu">
+ <li>
+ <a id="ctl00_hlSubNavShop" accesskey="j" title="Shop Geocaching" rel="external" href="http://shop.geocaching.com/">Shop Geocaching</a></li>
+ <li>
+ <a id="ctl00_hlSubNavIntlRetailers" title="International Retailers" rel="external" href="http://shop.geocaching.com/default/international-retailers/">International Retailers</a></li>
+ <li>
+ <a id="ctl00_hlSubNavGPSReviews" accesskey="w" title="GPS Reviews" href="/reviews/gps">GPS Reviews</a></li>
+ <li>
+ <a id="ctl00_hlSubNavGPSGuide" accesskey="k" title="Guide to Buying a GPS Device" href="../about/buying.aspx">Guide to Buying a GPS Device</a></li>
+ </ul>
+ </li>
+ <li>
+ <a id="ctl00_hlNavPartnering" accesskey="5" title="Partnering" href="../travel/">Partnering ▼</a>
+ <ul class="SubMenu">
+ <li>
+ <a id="ctl00_hlSubNavTravel" title="Travel and GeoTourism" href="../travel/">Travel and GeoTourism</a></li>
+ <li>
+ <a id="ctl00_hlSubNavBrandedPromotions" title="Branded Promotions" href="../brandedpromotions/">Branded Promotions</a></li>
+ <li>
+ <a id="ctl00_hlSubNavEducation" title="Geocaching and Education" href="../education/">Geocaching and Education</a></li>
+ <li>
+ <a id="ctl00_hlSubNavAdvertisingWithUs" title="Advertising with Us" href="../about/advertising.aspx">Advertising with Us</a></li>
+ <li>
+ <a id="ctl00_hlSubNavAPIProgram" title="API Program" href="../live/apidevelopers/">API Program</a></li>
+ </ul>
+ </li>
+ <li>
+ <a id="ctl00_hlNavVideos" accesskey="7" title="Videos" href="../videos/">Videos</a></li>
+ <li>
+ <a id="ctl00_hlNavFollowUs" title="Follow Us" href="http://www.facebook.com/geocaching">Follow Us ▼</a>
+ <ul class="SubMenu NavSocialMedia">
+ <li>
+ <a id="ctl00_hlSubNavFacebook" title="Facebook" class="SubNavFacebook" href="http://www.facebook.com/geocaching">Facebook</a></li>
+ <li>
+ <a id="ctl00_hlSubNavTwitter" title="Twitter" class="SubNavTwitter" href="http://twitter.com/GoGeocaching">Twitter</a></li>
+ <li>
+ <a id="ctl00_hlSubNavYouTube" title="YouTube" class="SubNavYouTube" href="http://www.youtube.com/user/GoGeocaching">YouTube</a></li>
+ </ul>
+ </li>
+ </ul>
+ </nav>
+ <div class="LanguageSelector">
+
+
+<div class="LocaleText">
+
+ <strong>Choose Your Language:</strong>
+
+</div>
+<div class="LocaleList">
+
+ <div class="selected-language">
+
+ <a href="#">English▼</a>
+
+ </div>
+ <ul class="language-list">
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl00_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl00$uxLocaleItem','')">English</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl01_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl01$uxLocaleItem','')">Deutsch</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl02_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl02$uxLocaleItem','')">Français</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl03_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl03$uxLocaleItem','')">Português</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl04_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl04$uxLocaleItem','')">Čeština</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl05_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl05$uxLocaleItem','')">Svenska</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl06_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl06$uxLocaleItem','')">Español</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl07_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl07$uxLocaleItem','')">Eesti</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl08_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl08$uxLocaleItem','')">Italiano</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl09_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl09$uxLocaleItem','')">Ελληνικά</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl10_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl10$uxLocaleItem','')">Latviešu</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl11_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl11$uxLocaleItem','')">Nederlands</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl12_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl12$uxLocaleItem','')">Català</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl13_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl13$uxLocaleItem','')">Polski</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl14_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl14$uxLocaleItem','')">Norsk, Bokmål</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl15_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl15$uxLocaleItem','')">한국어</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl16_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl16$uxLocaleItem','')">Magyar</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl17_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl17$uxLocaleItem','')">Română</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl18_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleListTop$uxLocaleList$ctl18$uxLocaleItem','')">日本語</a></li>
+
+ </ul>
+
+</div>
+<script type="text/javascript">
+
+ jQuery(document).ready(function () {
+ jQuery(".selected-language a").click(function (e) {
+ e.preventDefault();
+ var $loc = jQuery(this).parent().next();
+ jQuery($loc).show().position({
+ of: $loc.parent(),
+ my: "left top",
+ at: "left bottom",
+ offset: "0 0",
+ collision: "fit fit"
+ });
+ jQuery(this).addClass("Expanded");
+ jQuery(document).click(function () {
+ jQuery(".language-list").fadeOut("fast");
+ jQuery(".selected-language a").removeClass("Expanded");
+ });
+ return false;
+ });
+ });
+</script>
+ </div>
+ </div>
+ </div>
+ </header>
+ <section id="Content">
+
+ <div class="container">
+ <div id="ctl00_divBreadcrumbs" class="BreadcrumbWidget span-24 last">
+ <p>
+ <span id="ctl00_Breadcrumbs"><span><a title="Geocaching - The Official Global GPS Cache Hunt Site" href="/">Geocaching</a></span><span> > </span><span><a title="Trackables" href="/track/default.aspx">Trackables</a></span><span> > </span><span>Trackable Item Details</span></span>
+ </p>
+
+ </div>
+ <div id="ctl00_divContentMain" class="span-20">
+
+
+ <div id="ctl00_ContentBody_CoordInfoLinkControl1_uxCoordInfoLinkPanel" class="CoordInfoLinkWidget">
+
+ <p>
+ <a href="#" class="CoordInfoLink">
+ <span id="ctl00_ContentBody_CoordInfoLinkControl1_uxCoordInfoCode" class="CoordInfoCode">TB123E</span>
+ <span class="arrow">▼</span> </a>
+ </p>
+
+</div>
+<div id="dlgClipboard">
+ <input type="text" class="TextFormat" />
+ <a href="#" onclick="$('#dlgClipboard').hide();return false;" title="Close" class="Close">
+ x</a>
+</div>
+<script type="text/javascript">
+ $("a.CoordInfoLink").click(function (e) {
+ e.preventDefault();
+
+ $("#dlgClipboard")
+ .show()
+ .position({
+ of: $("a.CoordInfoLink"),
+ my: "right top",
+ at: "right bottom",
+ offset: "0 5"
+ })
+ .find("input")
+ .val('http://coord.info/' + $('.CoordInfoCode').text())
+ .focus()
+ .select();
+
+ $(document).mouseup(function (e) {
+ if ($(e.target).parent("div#dlgClipboard").length == 0) {
+ $(this).unbind(e);
+ $("div#dlgClipboard").hide();
+ }
+ });
+
+ return false;
+ });
+
+
+</script>
+
+ <h2 class="WrapFix">
+ <img id="ctl00_ContentBody_BugTypeImage" class="TravelBugHeaderIcon" src="http://www.geocaching.com/images/wpttypes/21.gif" alt="Travel Bug Dog Tag" style="border-width:0px;" />
+ <span id="ctl00_ContentBody_lbHeading">Travel Bug Dog Tag</span>
+ </h2>
+ <span id="ctl00_ContentBody_ErrorMessage"><p class="Warning"><img src="/images/icons/16/trackable_error.png" alt='Attention!' />This Trackable (<strong>Travel Bug Dog Tag</strong>) hasn't been activated. <a href="activate.aspx" title="Activate It Now">Activate it now</a>.</p></span>
+
+
+
+
+ <div class="Clear">
+ </div>
+ <p>
+ </p>
+
+ <div id="ctl00_ContentBody_SearchAgainPanel">
+
+ <div class="span-20">
+ <div class="InformationWidget">
+ <dl class="TrackableSearchForm">
+ <dt>
+ Enter the Tracking Code of the Item:</dt>
+ <dd>
+ <input name="ctl00$ContentBody$txtTrackingNumber" type="text" id="ctl00_ContentBody_txtTrackingNumber" maxlength="10" class="Text" />
+ <input type="submit" name="ctl00$ContentBody$btnLookupCode" value="Track" onclick="return false;" id="ctl00_ContentBody_btnLookupCode" />
+ </dd>
+ <dd>
+ <small><strong><em>
+ The 'Tracking Code' is the unique series of letters and numbers that appears on each item.
+ </em></strong></small>
+ </dd>
+ </dl>
+ </div>
+ </div>
+
+</div>
+
+ </div>
+ <div id="ctl00_divContentSide" class="span-4 last">
+ <div id="ctl00_uxBanManWidget" class="MasterPageAds" style="width:160px;">
+
+ <script type='text/javascript'>
+googletag.cmd.push(function() {{
+googletag.defineSlot('/1011121/trackables_pgs_160x600', [160, 600], 'div_5e86d8cd-24cd-4423-92b9-2cbc66470975').addService(googletag.pubads());
+googletag.pubads().enableSingleRequest();
+googletag.enableServices();
+}});
+</script>
+<div id='div_5e86d8cd-24cd-4423-92b9-2cbc66470975'>
+<script type='text/javascript'>
+googletag.cmd.push(function() { googletag.display('div_5e86d8cd-24cd-4423-92b9-2cbc66470975'); });
+</script>
+</div>
+
+ <p class="AlignCenter">
+ <small>
+ <a id="ctl00_hlAdvertiseWithUs" title="Advertising with Us" href="../about/advertising.aspx">Advertising with Us</a></small>
+ </p>
+
+</div>
+ </div>
+ </div>
+ </section>
+ <footer>
+ <div class="container">
+ <div class="span-24 last FooterTop">
+
+
+<div class="LocaleText">
+
+ <strong>Choose Your Language:</strong>
+
+</div>
+<div class="LocaleList">
+
+ <div class="selected-language">
+
+ <a href="#">English▼</a>
+
+ </div>
+ <ul class="language-list">
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl00_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem','')">English</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl01_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl01$uxLocaleItem','')">Deutsch</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl02_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl02$uxLocaleItem','')">Français</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl03_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl03$uxLocaleItem','')">Português</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl04_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl04$uxLocaleItem','')">Čeština</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl05_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl05$uxLocaleItem','')">Svenska</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl06_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl06$uxLocaleItem','')">Español</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl07_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl07$uxLocaleItem','')">Eesti</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl08_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl08$uxLocaleItem','')">Italiano</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl09_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl09$uxLocaleItem','')">Ελληνικά</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl10_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl10$uxLocaleItem','')">Latviešu</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl11_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl11$uxLocaleItem','')">Nederlands</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl12_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl12$uxLocaleItem','')">Català</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl13_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl13$uxLocaleItem','')">Polski</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl14_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl14$uxLocaleItem','')">Norsk, Bokmål</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl15_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl15$uxLocaleItem','')">한국어</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl16_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl16$uxLocaleItem','')">Magyar</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl17_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl17$uxLocaleItem','')">Română</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl18_uxLocaleItem" href="javascript:__doPostBack('ctl00$uxLocaleList$uxLocaleList$ctl18$uxLocaleItem','')">日本語</a></li>
+
+ </ul>
+
+</div>
+<script type="text/javascript">
+
+ jQuery(document).ready(function () {
+ jQuery(".selected-language a").click(function (e) {
+ e.preventDefault();
+ var $loc = jQuery(this).parent().next();
+ jQuery($loc).show().position({
+ of: $loc.parent(),
+ my: "left top",
+ at: "left bottom",
+ offset: "0 0",
+ collision: "fit fit"
+ });
+ jQuery(this).addClass("Expanded");
+ jQuery(document).click(function () {
+ jQuery(".language-list").fadeOut("fast");
+ jQuery(".selected-language a").removeClass("Expanded");
+ });
+ return false;
+ });
+ });
+</script>
+ </div>
+ <div class="span-4">
+ <p class="FooterHeader">
+ <strong>
+ About</strong>
+ </p>
+ <ul class="FooterLinks">
+ <li>
+ <a id="ctl00_hlFooterGlossary" title="Glossary of Terms" href="../about/glossary.aspx">Glossary of Terms</a></li>
+ <li>
+ <a id="ctl00_hlFooterBrochures" title="Brochures" href="../tools/#Guide">Brochures</a></li>
+ <li>
+ <a id="ctl00_hlFooterAbout" title="About Groundspeak" href="../about/groundspeak.aspx">About Groundspeak</a></li>
+ <li>
+ <a id="ctl00_hlFooterVolunteers" title="About Our Volunteers" href="../volunteers/">About Our Volunteers</a></li>
+ <li>
+ <a id="ctl00_hlFooterHistory" title="History" href="../about/history.aspx">History</a></li>
+ </ul>
+ </div>
+ <div class="span-4">
+ <p class="FooterHeader">
+ <strong>
+ Press</strong>
+ </p>
+ <ul class="FooterLinks">
+ <li>
+ <a id="ctl00_hlFooterNews" title="News Articles" href="../press/">News Articles</a></li>
+ <li>
+ <a id="ctl00_hlFooterMediaFAQs" title="Media FAQs" rel="document" href="../articles/Brochures/footer/FAQ_Media.pdf">Media FAQs</a></li>
+ <li>
+ <a id="ctl00_hlFooterMediaInquiries" title="Media Inquiries" rel="external" href="http://support.groundspeak.com/index.php?pg=request&xCategory=11">Media Inquiries</a></li>
+ <li>
+ <a id="ctl00_hlFooterLogo" accesskey="l" title="Logo Usage Guidelines" href="../about/logousage.aspx">Logo Usage Guidelines</a></li>
+ </ul>
+ </div>
+ <div class="span-5">
+ <p class="FooterHeader">
+ <strong>
+ Questions & Suggestions</strong>
+ </p>
+ <ul class="FooterLinks">
+ <li>
+ <a id="ctl00_hlFooterHelpCenterLink" title="Help Center" rel="external" href="http://support.groundspeak.com/index.php">Help Center</a></li>
+ <li>
+ <a id="ctl00_hlFooterDiscussionForums" accesskey="f" title="Discussion Forums" href="../forums/">Discussion Forums</a></li>
+ <li>
+ <a id="ctl00_hlFooterParksPoliceLink" title="Land Management and Law Enforcement" href="../parksandpolice/">Land Management and Law Enforcement</a></li>
+ <li>
+ <a id="ctl00_hlFooterContactUs" title="Contact Us" href="../contact/">Contact Us</a></li>
+ </ul>
+ </div>
+ <div class="span-4">
+ <p class="FooterHeader">
+ <strong>
+ Resources</strong>
+ </p>
+ <ul class="FooterLinks">
+ <li>
+ <a id="ctl00_hlFooterTools" accesskey="o" title="Tools and Downloads" href="../tools/">Tools and Downloads</a></li>
+ <li>
+ <a id="ctl00_hlFooterAPIProgram" title="API Program" href="../live/">API Program</a></li>
+ <li>
+ <a id="ctl00_hlFooterBenchmarks" title="Find a Benchmark" href="../mark/">Find a Benchmark</a></li>
+ </ul>
+ </div>
+ <div class="span-4 append-3 last">
+ <p class="FooterHeader">
+ <strong>
+ Follow Us</strong>
+ </p>
+ <ul class="FooterLinks FollowUsLinks">
+ <li>
+ <a id="ctl00_hlFacebook" title="Facebook" href="http://www.facebook.com/geocaching"><img id="ctl00_imgFacebook" title="Facebook" src="../images/home/icon_facebook.png" alt="Facebook" style="border-width:0px;" /></a></li>
+ <li>
+ <a id="ctl00_hlTwitter" title="Twitter" href="http://twitter.com/GoGeocaching"><img id="ctl00_imgTwitter" title="Twitter" src="../images/twitter/twitter_icon_white_22.png" alt="Twitter" style="border-width:0px;" /></a></li>
+ <li>
+ <a id="ctl00_hlYouTube" title="YouTube" href="http://www.youtube.com/user/GoGeocaching"><img id="ctl00_imgYouTube" title="YouTube" src="../images/home/icon_youtube.png" alt="YouTube" style="border-width:0px;" /></a></li>
+ </ul>
+ </div>
+ <p class="span-24 last FooterBottom">
+ Copyright
+ © 2000-2013
+ <a href="http://www.groundspeak.com/" title="Groundspeak, Inc." accesskey="g">Groundspeak, Inc.</a>
+ All Rights Reserved.<br />
+ <a id="ctl00_hlFooterTerms" accesskey="u" title="Groundspeak Terms of Use" href="../about/termsofuse.aspx">Groundspeak Terms of Use</a> (Updated: May 14, 2013)
+ |
+ <a id="ctl00_hlFooterPrivacy" accesskey="x" title="Privacy Policy" href="../about/privacypolicy.aspx">Privacy Policy</a> (Updated: May 14, 2013)
+ </p>
+ </div>
+ </footer>
+ <div class="SkipLinks">
+ <a id="ctl00_hlSkipLinksTop" accesskey="t" title="Return to the Top of the Page" href="#Top">Return to the Top of the Page</a>
+ </div>
+
+<script type="text/javascript">
+//<![CDATA[
+var Page_ValidationSummaries = new Array(document.getElementById("ctl00_vsSignInWidgetForm"));
+var Page_Validators = new Array(document.getElementById("ctl00_rfvUsername"), document.getElementById("ctl00_rfvPassword"));
+//]]>
+</script>
+
+<script type="text/javascript">
+//<![CDATA[
+var ctl00_vsSignInWidgetForm = document.all ? document.all["ctl00_vsSignInWidgetForm"] : document.getElementById("ctl00_vsSignInWidgetForm");
+ctl00_vsSignInWidgetForm.headertext = "<h4>Please correct the following issues:</h4>";
+ctl00_vsSignInWidgetForm.validationGroup = "SignInForm";
+var ctl00_rfvUsername = document.all ? document.all["ctl00_rfvUsername"] : document.getElementById("ctl00_rfvUsername");
+ctl00_rfvUsername.controltovalidate = "ctl00_tbUsername";
+ctl00_rfvUsername.errormessage = "Username is a required field; please enter a valid username.";
+ctl00_rfvUsername.display = "Dynamic";
+ctl00_rfvUsername.validationGroup = "SignInForm";
+ctl00_rfvUsername.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
+ctl00_rfvUsername.initialvalue = "";
+var ctl00_rfvPassword = document.all ? document.all["ctl00_rfvPassword"] : document.getElementById("ctl00_rfvPassword");
+ctl00_rfvPassword.controltovalidate = "ctl00_tbPassword";
+ctl00_rfvPassword.errormessage = "Password is a required field; please enter a valid password.";
+ctl00_rfvPassword.display = "Dynamic";
+ctl00_rfvPassword.validationGroup = "SignInForm";
+ctl00_rfvPassword.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
+ctl00_rfvPassword.initialvalue = "";
+//]]>
+</script>
+
+
+<script type="text/javascript">
+//<![CDATA[
+var gaToken = 'UA-2020240-1';
+document.getElementById('ctl00_vsSignInWidgetForm').dispose = function() {
+ Array.remove(Page_ValidationSummaries, document.getElementById('ctl00_vsSignInWidgetForm'));
+}
+
+var Page_ValidationActive = false;
+if (typeof(ValidatorOnLoad) == "function") {
+ ValidatorOnLoad();
+}
+
+function ValidatorOnSubmit() {
+ if (Page_ValidationActive) {
+ return ValidatorCommonOnSubmit();
+ }
+ else {
+ return true;
+ }
+}
+ WebForm_AutoFocus('btnSignIn');
+document.getElementById('ctl00_rfvUsername').dispose = function() {
+ Array.remove(Page_Validators, document.getElementById('ctl00_rfvUsername'));
+}
+
+document.getElementById('ctl00_rfvPassword').dispose = function() {
+ Array.remove(Page_Validators, document.getElementById('ctl00_rfvPassword'));
+}
+//]]>
+</script>
+</form>
+ <script type="text/javascript">
+ var browserType = {
+ IE: !!(window.attachEvent && !window.opera),
+ Opera: !!window.opera,
+ WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+ Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+ MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+ };
+
+ $(function () {
+ // Make the menu system play nice with all browsers:
+ $('ul.Menu li').hover(function () {
+ $(this).addClass('hover');
+ $('ul:first', this).css('visibility', 'visible');
+ }, function () {
+ $(this).removeClass('hover');
+ $('ul:first', this).css('visibility', 'hidden');
+ });
+ if (!isiOS()) {
+ // Constructing a Twitter-esque Login:
+ $(".SignInLink").click(function (e) {
+ e.preventDefault();
+ $("#SignInWidget").toggle();
+ $(".ProfileWidget").toggleClass("WidgetOpen");
+ $(this).blur();
+ $("#ctl00_tbUsername").focus();
+ });
+ $(".SignInCloseLink").click(function () {
+ $("#SignInWidget").toggle();
+ $(".ProfileWidget").toggleClass("WidgetOpen");
+ });
+ }
+ $('.SignedInProfileLink').truncate({
+ width: 120,
+ after: '&hellip;',
+ center: false,
+ addclass: false,
+ addtitle: false
+ });
+
+ // Hide the warning message if the user closed it already
+ if ($.cookie('hide_warning') != null) {
+ $(".WarningMessage").hide();
+ } else {
+ $("#warningCloseButton").click(function () {
+ $('.WarningMessage').hide('blind');
+ $.cookie('hide_warning', 'true', { expires: 1 });
+ });
+ }
+
+ function isiOS() {
+ return (
+ (navigator.userAgent.match(/(iPhone)|(iPod)|(iPad)/i))
+ );
+ }
+ });
+ </script>
+
+ <script type="text/javascript" language="javascript">
+ $(function () {
+ $("a.tb_images").fancybox({ 'titlePosition': 'inside' });
+ });
+
+ $("#ctl00_ContentBody_btnLookupCode").click(function() {
+ if ($("#ctl00_ContentBody_txtTrackingNumber").val().length > 0) {
+ window.location = "/track/details.aspx?tracker=" + $("#ctl00_ContentBody_txtTrackingNumber").val();
+ } else {
+ alert("Please enter a tracking code to search for...");
+ window.setTimeout(hideModalSpinner(), 50);
+ }
+ });
+ </script>
+
+ <script type="text/javascript">
+ _gaq.push(['_require', 'inpage_linkid', '//www.google-analytics.com/plugins/ga/inpage_linkid.js']);
+ _gaq.push(['_setAccount', gaToken]);
+ _gaq.push(['_trackPageview']);
+ (function () {
+ var ga = document.createElement('script');
+ ga.src = ('https:' == document.location.protocol ?
+ 'https://ssl' : 'http://www') +
+ '.google-analytics.com/ga.js';
+ ga.setAttribute('async', 'true');
+ document.documentElement.firstChild.appendChild(ga);
+ })();
+ $(function () {
+ $("a.language").click(function (e) {
+ e.preventDefault();
+ window.location.replace(window.location.href + (window.location.search.indexOf("?") == -1 ? "?" : "&") + "lang=" + $(this).attr("lang"));
+ });
+ });
+ </script>
+ <!-- Quantcast Tag -->
+ <div id="Quantcast">
+ <script type="text/javascript">
+ var _qevents = _qevents || [];
+
+ (function () {
+ var elem = document.createElement('script');
+
+ elem.src = (document.location.protocol == "https:" ? "https://secure" : "http://edge") + ".quantserve.com/quant.js";
+ elem.async = true;
+ elem.type = "text/javascript";
+ var scpt = document.getElementsByTagName('script')[0];
+ scpt.parentNode.insertBefore(elem, scpt);
+ })();
+ </script>
+ <script type="text/javascript">
+ _qevents.push({ qacct: "p-f6VPrfmR4cujU" });
+ </script>
+ <noscript>
+ <div style="display: none;">
+ <img src="http://pixel.quantserve.com/pixel/p-f6VPrfmR4cujU.gif" height="1" width="1"
+ alt="Quantcast" />
+ </div>
+ </noscript>
+ </div>
+ <!-- End Quantcast tag -->
+ <!-- Server: WEB15; Build: Web.HotFix_20130611.1 -->
+</body>
+</html>
diff --git a/tests/src/cgeo/geocaching/TrackableTest.java b/tests/src/cgeo/geocaching/TrackableTest.java index 2852a4d..7d3fd5c 100644 --- a/tests/src/cgeo/geocaching/TrackableTest.java +++ b/tests/src/cgeo/geocaching/TrackableTest.java @@ -5,8 +5,7 @@ import android.test.AndroidTestCase; public class TrackableTest extends AndroidTestCase { public static void testGetGeocode() { - final Trackable trackable = new Trackable(); - trackable.setGeocode("tb1234"); + final Trackable trackable = createTrackable("tb1234"); assertEquals("TB1234", trackable.getGeocode()); } @@ -15,4 +14,26 @@ public class TrackableTest extends AndroidTestCase { trackable.setLogs(null); assertNotNull("Trackable logs must not be null!", trackable.getLogs()); } + + public static void testTrackableUrl() { + final Trackable trackable = createTrackable("TB1234"); + assertEquals("http://www.geocaching.com//track/details.aspx?tracker=TB1234", trackable.getUrl()); + } + + public static void testGeokretUrl() { + Trackable geokret = createTrackable("GK82A2"); + assertEquals("http://geokrety.org/konkret.php?id=33442", geokret.getUrl()); + } + + public static void testLoggable() { + assertTrue(createTrackable("TB1234").isLoggable()); + assertFalse(createTrackable("GK1234").isLoggable()); + } + + private static Trackable createTrackable(String geocode) { + final Trackable trackable = new Trackable(); + trackable.setGeocode(geocode); + return trackable; + } + } diff --git a/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java b/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java index 94cc067..8d3d840 100644 --- a/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java +++ b/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java @@ -3,6 +3,7 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.trackable.TravelBugConnector; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; @@ -46,10 +47,16 @@ public class GCConnectorTest extends AbstractResourceInstrumentationTestCase { public static void testCanHandle() { assertTrue(GCConnector.getInstance().canHandle("GC2MEGA")); - assertTrue(GCConnector.getInstance().canHandle("TB3F651")); assertFalse(GCConnector.getInstance().canHandle("OXZZZZZ")); } + /** + * functionality moved to {@link TravelBugConnector} + */ + public static void testCanNotHandleTrackablesAnymore() { + assertFalse(GCConnector.getInstance().canHandle("TB3F651")); + } + public static void testBaseCodings() { assertEquals(2045702, GCConstants.gccodeToGCId("GC2MEGA")); } diff --git a/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java b/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java index 9bc2caf..45eee3b 100644 --- a/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java +++ b/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java @@ -4,7 +4,9 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.Image; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; +import cgeo.geocaching.Trackable; import cgeo.geocaching.Waypoint; +import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.enumerations.WaypointType; @@ -213,4 +215,12 @@ public class GCParserTest extends AbstractResourceInstrumentationTestCase { return result.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); } + public void testTrackableNotActivated() { + final String page = getFileContent(R.raw.tb123e_html); + final Trackable trackable = GCParser.parseTrackable(page, "TB123E"); + assertNotNull(trackable); + assertEquals("TB123E", trackable.getGeocode()); + final String expectedDetails = cgeoapplication.getInstance().getString(cgeo.geocaching.R.string.trackable_not_activated); + assertEquals(expectedDetails, trackable.getDetails()); + } } diff --git a/tests/src/cgeo/geocaching/connector/trackable/GeokretyConnectorTest.java b/tests/src/cgeo/geocaching/connector/trackable/GeokretyConnectorTest.java new file mode 100644 index 0000000..f08fb6b --- /dev/null +++ b/tests/src/cgeo/geocaching/connector/trackable/GeokretyConnectorTest.java @@ -0,0 +1,14 @@ +package cgeo.geocaching.connector.trackable; + +import junit.framework.TestCase; + +public class GeokretyConnectorTest extends TestCase { + + public static void testCanHandleTrackable() { + assertTrue(new GeokretyConnector().canHandleTrackable("GK82A2")); + assertFalse(new GeokretyConnector().canHandleTrackable("GKXYZ1")); // non hex + assertFalse(new GeokretyConnector().canHandleTrackable("TB1234")); + assertFalse(new GeokretyConnector().canHandleTrackable("UNKNOWN")); + } + +} diff --git a/tests/src/cgeo/geocaching/connector/trackable/TravelBugConnectorTest.java b/tests/src/cgeo/geocaching/connector/trackable/TravelBugConnectorTest.java new file mode 100644 index 0000000..7772e29 --- /dev/null +++ b/tests/src/cgeo/geocaching/connector/trackable/TravelBugConnectorTest.java @@ -0,0 +1,24 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +import junit.framework.TestCase; + +public class TravelBugConnectorTest extends TestCase { + + public static void testCanHandleTrackable() { + assertTrue(new TravelBugConnector().canHandleTrackable("TB1234")); + assertTrue(new TravelBugConnector().canHandleTrackable("TB1")); + assertTrue(new TravelBugConnector().canHandleTrackable("TB123F")); + assertTrue(new TravelBugConnector().canHandleTrackable("TB123Z")); + assertFalse(new TravelBugConnector().canHandleTrackable("GK1234")); + assertFalse(new TravelBugConnector().canHandleTrackable("UNKNOWN")); + } + + public static void testGetUrl() { + final Trackable trackable = new Trackable(); + trackable.setGeocode("TB2345"); + assertEquals("http://www.geocaching.com//track/details.aspx?tracker=TB2345", new TravelBugConnector().getUrl(trackable)); + } + +} |
