aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBananeweizen <bananeweizen@gmx.de>2013-06-16 08:04:52 +0200
committerBananeweizen <bananeweizen@gmx.de>2013-06-16 08:04:52 +0200
commit08a33325f9872d0b3b37eac7028d5540a5330d08 (patch)
tree5b150863ad9c1bbe6a6e432005aa640069da44ee
parent3bb09ef0006c9a5bab27852cff848af72d0a2b16 (diff)
downloadcgeo-08a33325f9872d0b3b37eac7028d5540a5330d08.zip
cgeo-08a33325f9872d0b3b37eac7028d5540a5330d08.tar.gz
cgeo-08a33325f9872d0b3b37eac7028d5540a5330d08.tar.bz2
fix #2886: trackable quick search not working
* introduce separate trackable connectors * new detection of not activated trackables * fix geokrety urls
-rw-r--r--main/res/values-de/strings.xml3
-rw-r--r--main/res/values/strings.xml3
-rw-r--r--main/src/cgeo/geocaching/SearchActivity.java21
-rw-r--r--main/src/cgeo/geocaching/Trackable.java23
-rw-r--r--main/src/cgeo/geocaching/connector/ConnectorFactory.java48
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConnector.java7
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConstants.java9
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCParser.java141
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java10
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java32
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java17
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java28
-rw-r--r--main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java19
-rw-r--r--tests/res/raw/tb123e_html.html877
-rw-r--r--tests/src/cgeo/geocaching/TrackableTest.java25
-rw-r--r--tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java9
-rw-r--r--tests/src/cgeo/geocaching/connector/gc/GCParserTest.java10
-rw-r--r--tests/src/cgeo/geocaching/connector/trackable/GeokretyConnectorTest.java14
-rw-r--r--tests/src/cgeo/geocaching/connector/trackable/TravelBugConnectorTest.java24
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&amp;t=634981136634411315" type="text/javascript"></script>
+
+
+<script src="/ScriptResource.axd?d=8q09cuy9-PZDS8eZx6HLVXd3YZywgUmuoy8VVtGr3vaISS8ybaBLyQLFlq93nr2dlYP0QJWcJvooQKkloTdALTrS571k1UcPpD2pbXbpthIOf2Mn8gCnpyvfv-ZudWTMi0y39YZC0-Q0yjjxQ82yTlKdhBw1&amp;t=150492e7" type="text/javascript"></script>
+<script src="/ScriptResource.axd?d=I9_m2Hb1Tv_B0qTMDG8bMbnkNSHUkv5oUaG9-V5NZ8qQ2VFlu60I8y8gfr3vPmZjbiPnu43MOQdFVDeYF-nDAEKBLmyxD3DCTGmes9NNbbvaDEHyEuuRWgccIkK3ik5TI48YGDxjHjqdn-gTK4Fkgd17LGw1&amp;t=ffffffff940d030f" type="text/javascript"></script>
+<script src="/ScriptResource.axd?d=8vNbe34dAujgZMPnfnacfjeoweX1vHgyns8KlAV4vpGpsZC9Cf3pro__lv8ekBa0NiCgXGMMolzOUNH__lrnEI_qjlNBIAuuLeemtAXV_i6E0QIMZa8nGSYmWGF5nQOJK3rmZzvTxsr2Mh4Ebdba_1ywGLUSH_U_XIe-jzecfRQwwvjZ0&amp;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&amp;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(&#39;ctl00$uxSignIn&#39;,&#39;&#39;)"><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>&nbsp;|&nbsp;<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(&quot;ctl00$btnSignIn&quot;, &quot;&quot;, true, &quot;SignInForm&quot;, &quot;https://www.geocaching.com/login/default.aspx?redir=%2ftrack%2fdetails.aspx%3ftracker%3dTB123E&quot;, 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 &#9660;</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&amp;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 &#9660;</a>
+ <ul class="SubMenu">
+ <li>
+ <a id="ctl00_hlSubNavHide" accesskey="d" title="Hide &amp; 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 &#9660;</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 &#9660;</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 &#9660;</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 &#9660;</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&#9660;</a>
+
+ </div>
+ <ul class="language-list">
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl00_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl00$uxLocaleItem&#39;,&#39;&#39;)">English</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl01_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl01$uxLocaleItem&#39;,&#39;&#39;)">Deutsch</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl02_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl02$uxLocaleItem&#39;,&#39;&#39;)">Français</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl03_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl03$uxLocaleItem&#39;,&#39;&#39;)">Português</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl04_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl04$uxLocaleItem&#39;,&#39;&#39;)">Čeština</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl05_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl05$uxLocaleItem&#39;,&#39;&#39;)">Svenska</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl06_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl06$uxLocaleItem&#39;,&#39;&#39;)">Español</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl07_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl07$uxLocaleItem&#39;,&#39;&#39;)">Eesti</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl08_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl08$uxLocaleItem&#39;,&#39;&#39;)">Italiano</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl09_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl09$uxLocaleItem&#39;,&#39;&#39;)">Ελληνικά</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl10_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl10$uxLocaleItem&#39;,&#39;&#39;)">Latviešu</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl11_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl11$uxLocaleItem&#39;,&#39;&#39;)">Nederlands</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl12_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl12$uxLocaleItem&#39;,&#39;&#39;)">Català</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl13_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl13$uxLocaleItem&#39;,&#39;&#39;)">Polski</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl14_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl14$uxLocaleItem&#39;,&#39;&#39;)">Norsk, Bokmål</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl15_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl15$uxLocaleItem&#39;,&#39;&#39;)">한국어</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl16_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl16$uxLocaleItem&#39;,&#39;&#39;)">Magyar</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl17_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl17$uxLocaleItem&#39;,&#39;&#39;)">Română</a></li>
+
+ <li><a id="ctl00_uxLocaleListTop_uxLocaleList_ctl18_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleListTop$uxLocaleList$ctl18$uxLocaleItem&#39;,&#39;&#39;)">日本語</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> &gt; </span><span><a title="Trackables" href="/track/default.aspx">Trackables</a></span><span> &gt; </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">&#9660;</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&#9660;</a>
+
+ </div>
+ <ul class="language-list">
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl00_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem&#39;,&#39;&#39;)">English</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl01_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl01$uxLocaleItem&#39;,&#39;&#39;)">Deutsch</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl02_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl02$uxLocaleItem&#39;,&#39;&#39;)">Français</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl03_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl03$uxLocaleItem&#39;,&#39;&#39;)">Português</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl04_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl04$uxLocaleItem&#39;,&#39;&#39;)">Čeština</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl05_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl05$uxLocaleItem&#39;,&#39;&#39;)">Svenska</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl06_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl06$uxLocaleItem&#39;,&#39;&#39;)">Español</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl07_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl07$uxLocaleItem&#39;,&#39;&#39;)">Eesti</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl08_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl08$uxLocaleItem&#39;,&#39;&#39;)">Italiano</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl09_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl09$uxLocaleItem&#39;,&#39;&#39;)">Ελληνικά</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl10_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl10$uxLocaleItem&#39;,&#39;&#39;)">Latviešu</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl11_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl11$uxLocaleItem&#39;,&#39;&#39;)">Nederlands</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl12_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl12$uxLocaleItem&#39;,&#39;&#39;)">Català</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl13_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl13$uxLocaleItem&#39;,&#39;&#39;)">Polski</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl14_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl14$uxLocaleItem&#39;,&#39;&#39;)">Norsk, Bokmål</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl15_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl15$uxLocaleItem&#39;,&#39;&#39;)">한국어</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl16_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl16$uxLocaleItem&#39;,&#39;&#39;)">Magyar</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl17_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl17$uxLocaleItem&#39;,&#39;&#39;)">Română</a></li>
+
+ <li><a id="ctl00_uxLocaleList_uxLocaleList_ctl18_uxLocaleItem" href="javascript:__doPostBack(&#39;ctl00$uxLocaleList$uxLocaleList$ctl18$uxLocaleItem&#39;,&#39;&#39;)">日本語</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&amp;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&nbsp;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
+ &copy; 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: '&amp;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));
+ }
+
+}