aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2013-04-07 13:35:30 +0200
committerSamuel Tardieu <sam@rfc1149.net>2013-04-07 13:46:01 +0200
commit29b9c9bcb06cb19f39ead0184b05ae8c96eda9cb (patch)
tree97fc1156426c0b623ff0a53de0c4fda8a7c89473
parentc1b68d1b4b5d73afe60dcbbe03e9d3fd12f428b0 (diff)
parent37c90405dff48b5996fc8103e071fcd9c54f7a21 (diff)
downloadcgeo-29b9c9bcb06cb19f39ead0184b05ae8c96eda9cb.zip
cgeo-29b9c9bcb06cb19f39ead0184b05ae8c96eda9cb.tar.gz
cgeo-29b9c9bcb06cb19f39ead0184b05ae8c96eda9cb.tar.bz2
Merge branch 'release' into upstream
Conflicts: main/src/cgeo/geocaching/export/GpxExport.java
-rw-r--r--main/src/cgeo/geocaching/export/GpxExport.java6
-rw-r--r--main/src/cgeo/org/kxml2/io/KXmlSerializer.java605
-rw-r--r--tests/src/cgeo/geocaching/export/ExportTest.java52
3 files changed, 660 insertions, 3 deletions
diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java
index c6f72fd..3e58276 100644
--- a/main/src/cgeo/geocaching/export/GpxExport.java
+++ b/main/src/cgeo/geocaching/export/GpxExport.java
@@ -15,6 +15,7 @@ import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.BaseUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.XmlUtils;
+import cgeo.org.kxml2.io.KXmlSerializer;
import org.apache.commons.lang3.StringUtils;
import org.xmlpull.v1.XmlSerializer;
@@ -26,7 +27,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
-import android.util.Xml;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.CheckBox;
@@ -114,7 +114,7 @@ class GpxExport extends AbstractExport {
return allGeocodes.toArray(new String[allGeocodes.size()]);
}
- private class ExportTask extends AsyncTaskWithProgress<String, File> {
+ protected class ExportTask extends AsyncTaskWithProgress<String, File> {
private final Activity activity;
private int countExported = 0;
@@ -147,7 +147,7 @@ class GpxExport extends AbstractExport {
final File exportLocation = new File(Settings.getGpxExportDir());
exportLocation.mkdirs();
- final XmlSerializer gpx = Xml.newSerializer();
+ final XmlSerializer gpx = new KXmlSerializer();
writer = new FileWriter(exportFile);
gpx.setOutput(writer);
diff --git a/main/src/cgeo/org/kxml2/io/KXmlSerializer.java b/main/src/cgeo/org/kxml2/io/KXmlSerializer.java
new file mode 100644
index 0000000..027ff53
--- /dev/null
+++ b/main/src/cgeo/org/kxml2/io/KXmlSerializer.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+package cgeo.org.kxml2.io;
+
+import org.apache.commons.lang3.StringUtils;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Locale;
+
+public class KXmlSerializer implements XmlSerializer {
+
+ // static final String UNDEFINED = ":";
+
+ // BEGIN android-added
+ /** size (in characters) for the write buffer */
+ private static final int WRITE_BUFFER_SIZE = 500;
+ // END android-added
+
+ // BEGIN android-changed
+ // (Guarantee that the writer is always buffered.)
+ private BufferedWriter writer;
+ // END android-changed
+
+ private boolean pending;
+ private int auto;
+ private int depth;
+
+ private String[] elementStack = new String[12];
+ //nsp/prefix/name
+ private int[] nspCounts = new int[4];
+ private String[] nspStack = new String[8];
+ //prefix/nsp; both empty are ""
+ private boolean[] indent = new boolean[4];
+ private boolean unicode;
+ private String encoding;
+
+ private final void check(boolean close) throws IOException {
+ if (!pending) {
+ return;
+ }
+
+ depth++;
+ pending = false;
+
+ if (indent.length <= depth) {
+ boolean[] hlp = new boolean[depth + 4];
+ System.arraycopy(indent, 0, hlp, 0, depth);
+ indent = hlp;
+ }
+ indent[depth] = indent[depth - 1];
+
+ for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) {
+ writer.write(' ');
+ writer.write("xmlns");
+ if (!StringUtils.isEmpty(nspStack[i * 2])) {
+ writer.write(':');
+ writer.write(nspStack[i * 2]);
+ }
+ else if (StringUtils.isEmpty(getNamespace()) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
+ throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
+ }
+ writer.write("=\"");
+ writeEscaped(nspStack[i * 2 + 1], '"');
+ writer.write('"');
+ }
+
+ if (nspCounts.length <= depth + 1) {
+ int[] hlp = new int[depth + 8];
+ System.arraycopy(nspCounts, 0, hlp, 0, depth + 1);
+ nspCounts = hlp;
+ }
+
+ nspCounts[depth + 1] = nspCounts[depth];
+ // nspCounts[depth + 2] = nspCounts[depth];
+
+ writer.write(close ? " />" : ">");
+ }
+
+ private final void writeEscaped(String s, int quot) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '\n':
+ case '\r':
+ case '\t':
+ if (quot == -1) {
+ writer.write(c);
+ } else {
+ writer.write("&#"+((int) c)+';');
+ }
+ break;
+ case '&':
+ writer.write("&amp;");
+ break;
+ case '>':
+ writer.write("&gt;");
+ break;
+ case '<':
+ writer.write("&lt;");
+ break;
+ default:
+ if (c == quot) {
+ writer.write(c == '"' ? "&quot;" : "&apos;");
+ break;
+ }
+ // BEGIN android-changed: refuse to output invalid characters
+ // See http://www.w3.org/TR/REC-xml/#charsets for definition.
+ // Corrected for c:geo to handle utf-16 codepoint surrogates correctly
+ // See http://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000_to_U.2B10FFFF
+ // Note: tab, newline, and carriage return have already been
+ // handled above.
+ // Check for lead surrogate
+ if (c >= 0xd800 && c <= 0xdbff) {
+
+ if (i + 1 < s.length()) {
+ writer.write(s.substring(i, i + 1));
+ i++;
+ break;
+ }
+ // if the lead surrogate is at the string end, it's not valid utf-16
+ reportInvalidCharacter(c);
+ }
+ boolean valid = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
+ if (!valid) {
+ reportInvalidCharacter(c);
+ }
+ if (unicode || c < 127) {
+ writer.write(c);
+ } else {
+ writer.write("&#" + ((int) c) + ";");
+ }
+ // END android-changed
+ }
+ }
+ }
+
+ // BEGIN android-added
+ private static void reportInvalidCharacter(char ch) {
+ throw new IllegalArgumentException("Illegal character (" + Integer.toHexString((int) ch) + ")");
+ }
+ // END android-added
+
+ /*
+ * private final void writeIndent() throws IOException {
+ * writer.write("\r\n");
+ * for (int i = 0; i < depth; i++)
+ * writer.write(' ');
+ * }
+ */
+
+ public void docdecl(String dd) throws IOException {
+ writer.write("<!DOCTYPE");
+ writer.write(dd);
+ writer.write(">");
+ }
+
+ public void endDocument() throws IOException {
+ while (depth > 0) {
+ endTag(elementStack[depth * 3 - 3], elementStack[depth * 3 - 1]);
+ }
+ flush();
+ }
+
+ public void entityRef(String name) throws IOException {
+ check(false);
+ writer.write('&');
+ writer.write(name);
+ writer.write(';');
+ }
+
+ public boolean getFeature(String name) {
+ //return false;
+ return ("http://xmlpull.org/v1/doc/features.html#indent-output"
+ .equals(
+ name))
+ ? indent[depth]
+ : false;
+ }
+
+ public String getPrefix(String namespace, boolean create) {
+ try {
+ return getPrefix(namespace, false, create);
+ } catch (IOException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ private final String getPrefix(
+ String namespace,
+ boolean includeDefault,
+ boolean create)
+ throws IOException {
+
+ for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
+ if (nspStack[i + 1].equals(namespace)
+ && (includeDefault || !StringUtils.isEmpty(nspStack[i]))) {
+ String cand = nspStack[i];
+ for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) {
+ if (nspStack[j].equals(cand)) {
+ cand = null;
+ break;
+ }
+ }
+ if (cand != null) {
+ return cand;
+ }
+ }
+ }
+
+ if (!create) {
+ return null;
+ }
+
+ String prefix;
+
+ if (StringUtils.isEmpty(namespace)) {
+ prefix = "";
+ } else {
+ do {
+ prefix = "n" + (auto++);
+ for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
+ if (prefix.equals(nspStack[i])) {
+ prefix = null;
+ break;
+ }
+ }
+ } while (prefix == null);
+ }
+
+ boolean p = pending;
+ pending = false;
+ setPrefix(prefix, namespace);
+ pending = p;
+ return prefix;
+ }
+
+ public Object getProperty(String name) {
+ throw new RuntimeException("Unsupported property");
+ }
+
+ public void ignorableWhitespace(String s)
+ throws IOException {
+ text(s);
+ }
+
+ public void setFeature(String name, boolean value) {
+ if ("http://xmlpull.org/v1/doc/features.html#indent-output"
+ .equals(name)) {
+ indent[depth] = value;
+ } else {
+ throw new RuntimeException("Unsupported Feature");
+ }
+ }
+
+ public void setProperty(String name, Object value) {
+ throw new RuntimeException(
+ "Unsupported Property:" + value);
+ }
+
+ public void setPrefix(String prefix, String namespace)
+ throws IOException {
+
+ check(false);
+ if (prefix == null) {
+ prefix = "";
+ }
+ if (namespace == null) {
+ namespace = "";
+ }
+
+ String defined = getPrefix(namespace, true, false);
+
+ // boil out if already defined
+
+ if (prefix.equals(defined)) {
+ return;
+ }
+
+ int pos = (nspCounts[depth + 1]++) << 1;
+
+ if (nspStack.length < pos + 1) {
+ String[] hlp = new String[nspStack.length + 16];
+ System.arraycopy(nspStack, 0, hlp, 0, pos);
+ nspStack = hlp;
+ }
+
+ nspStack[pos++] = prefix;
+ nspStack[pos] = namespace;
+ }
+
+ public void setOutput(Writer writer) {
+ // BEGIN android-changed
+ // Guarantee that the writer is always buffered.
+ if (writer instanceof BufferedWriter) {
+ this.writer = (BufferedWriter) writer;
+ } else {
+ this.writer = new BufferedWriter(writer, WRITE_BUFFER_SIZE);
+ }
+ // END android-changed
+
+ // elementStack = new String[12]; //nsp/prefix/name
+ //nspCounts = new int[4];
+ //nspStack = new String[8]; //prefix/nsp
+ //indent = new boolean[4];
+
+ nspCounts[0] = 2;
+ nspCounts[1] = 2;
+ nspStack[0] = "";
+ nspStack[1] = "";
+ nspStack[2] = "xml";
+ nspStack[3] = "http://www.w3.org/XML/1998/namespace";
+ pending = false;
+ auto = 0;
+ depth = 0;
+
+ unicode = false;
+ }
+
+ public void setOutput(OutputStream os, String encoding)
+ throws IOException {
+ if (os == null) {
+ throw new IllegalArgumentException("os == null");
+ }
+ setOutput(encoding == null
+ ? new OutputStreamWriter(os)
+ : new OutputStreamWriter(os, encoding));
+ this.encoding = encoding;
+ if (encoding != null && encoding.toLowerCase(Locale.US).startsWith("utf")) {
+ unicode = true;
+ }
+ }
+
+ public void startDocument(String encoding, Boolean standalone) throws IOException {
+ writer.write("<?xml version='1.0' ");
+
+ if (encoding != null) {
+ this.encoding = encoding;
+ if (encoding.toLowerCase(Locale.US).startsWith("utf")) {
+ unicode = true;
+ }
+ }
+
+ if (this.encoding != null) {
+ writer.write("encoding='");
+ writer.write(this.encoding);
+ writer.write("' ");
+ }
+
+ if (standalone != null) {
+ writer.write("standalone='");
+ writer.write(
+ standalone.booleanValue() ? "yes" : "no");
+ writer.write("' ");
+ }
+ writer.write("?>");
+ }
+
+ public XmlSerializer startTag(String namespace, String name)
+ throws IOException {
+ check(false);
+
+ // if (namespace == null)
+ // namespace = "";
+
+ if (indent[depth]) {
+ writer.write("\r\n");
+ for (int i = 0; i < depth; i++) {
+ writer.write(" ");
+ }
+ }
+
+ int esp = depth * 3;
+
+ if (elementStack.length < esp + 3) {
+ String[] hlp = new String[elementStack.length + 12];
+ System.arraycopy(elementStack, 0, hlp, 0, esp);
+ elementStack = hlp;
+ }
+
+ String prefix =
+ namespace == null
+ ? ""
+ : getPrefix(namespace, true, true);
+
+ if (namespace != null && StringUtils.isEmpty(namespace)) {
+ for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++) {
+ if (StringUtils.isEmpty(nspStack[i * 2]) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
+ throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
+ }
+ }
+ }
+
+ elementStack[esp++] = namespace;
+ elementStack[esp++] = prefix;
+ elementStack[esp] = name;
+
+ writer.write('<');
+ if (!StringUtils.isEmpty(prefix)) {
+ writer.write(prefix);
+ writer.write(':');
+ }
+
+ writer.write(name);
+
+ pending = true;
+
+ return this;
+ }
+
+ public XmlSerializer attribute(
+ String namespace,
+ String name,
+ String value)
+ throws IOException {
+ if (!pending) {
+ throw new IllegalStateException("illegal position for attribute");
+ }
+
+ // int cnt = nspCounts[depth];
+
+ if (namespace == null) {
+ namespace = "";
+ }
+
+ // depth--;
+ // pending = false;
+
+ String prefix =
+ StringUtils.isEmpty(namespace)
+ ? ""
+ : getPrefix(namespace, false, true);
+
+ // pending = true;
+ // depth++;
+
+ /*
+ * if (cnt != nspCounts[depth]) {
+ * writer.write(' ');
+ * writer.write("xmlns");
+ * if (nspStack[cnt * 2] != null) {
+ * writer.write(':');
+ * writer.write(nspStack[cnt * 2]);
+ * }
+ * writer.write("=\"");
+ * writeEscaped(nspStack[cnt * 2 + 1], '"');
+ * writer.write('"');
+ * }
+ */
+
+ writer.write(' ');
+ if (!StringUtils.isEmpty(prefix)) {
+ writer.write(prefix);
+ writer.write(':');
+ }
+ writer.write(name);
+ writer.write('=');
+ char q = value.indexOf('"') == -1 ? '"' : '\'';
+ writer.write(q);
+ writeEscaped(value, q);
+ writer.write(q);
+
+ return this;
+ }
+
+ public void flush() throws IOException {
+ check(false);
+ writer.flush();
+ }
+
+ /*
+ * public void close() throws IOException {
+ * check();
+ * writer.close();
+ * }
+ */
+ public XmlSerializer endTag(String namespace, String name)
+ throws IOException {
+
+ if (!pending)
+ {
+ depth--;
+ // if (namespace == null)
+ // namespace = "";
+ }
+
+ if ((namespace == null
+ && elementStack[depth * 3] != null)
+ || (namespace != null
+ && !namespace.equals(elementStack[depth * 3]))
+ || !elementStack[depth * 3 + 2].equals(name)) {
+ throw new IllegalArgumentException("</{"+namespace+"}"+name+"> does not match start");
+ }
+
+ if (pending) {
+ check(true);
+ depth--;
+ }
+ else {
+ if (indent[depth + 1]) {
+ writer.write("\r\n");
+ for (int i = 0; i < depth; i++) {
+ writer.write(" ");
+ }
+ }
+
+ writer.write("</");
+ String prefix = elementStack[depth * 3 + 1];
+ if (!StringUtils.isEmpty(prefix)) {
+ writer.write(prefix);
+ writer.write(':');
+ }
+ writer.write(name);
+ writer.write('>');
+ }
+
+ nspCounts[depth + 1] = nspCounts[depth];
+ return this;
+ }
+
+ public String getNamespace() {
+ return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 3];
+ }
+
+ public String getName() {
+ return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 1];
+ }
+
+ public int getDepth() {
+ return pending ? depth + 1 : depth;
+ }
+
+ public XmlSerializer text(String text) throws IOException {
+ check(false);
+ indent[depth] = false;
+ writeEscaped(text, -1);
+ return this;
+ }
+
+ public XmlSerializer text(char[] text, int start, int len)
+ throws IOException {
+ text(new String(text, start, len));
+ return this;
+ }
+
+ public void cdsect(String data) throws IOException {
+ check(false);
+ // BEGIN android-changed: ]]> is not allowed within a CDATA,
+ // so break and start a new one when necessary.
+ data = data.replace("]]>", "]]]]><![CDATA[>");
+ char[] chars = data.toCharArray();
+ // We also aren't allowed any invalid characters.
+ for (char ch : chars) {
+ boolean valid = (ch >= 0x20 && ch <= 0xd7ff) ||
+ (ch == '\t' || ch == '\n' || ch == '\r') ||
+ (ch >= 0xe000 && ch <= 0xfffd);
+ if (!valid) {
+ reportInvalidCharacter(ch);
+ }
+ }
+ writer.write("<![CDATA[");
+ writer.write(chars, 0, chars.length);
+ writer.write("]]>");
+ // END android-changed
+ }
+
+ public void comment(String comment) throws IOException {
+ check(false);
+ writer.write("<!--");
+ writer.write(comment);
+ writer.write("-->");
+ }
+
+ public void processingInstruction(String pi)
+ throws IOException {
+ check(false);
+ writer.write("<?");
+ writer.write(pi);
+ writer.write("?>");
+ }
+} \ No newline at end of file
diff --git a/tests/src/cgeo/geocaching/export/ExportTest.java b/tests/src/cgeo/geocaching/export/ExportTest.java
index 22c6dd2..60d5716 100644
--- a/tests/src/cgeo/geocaching/export/ExportTest.java
+++ b/tests/src/cgeo/geocaching/export/ExportTest.java
@@ -3,7 +3,15 @@ package cgeo.geocaching.export;
import cgeo.CGeoTestCase;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.LogEntry;
+import cgeo.geocaching.cgData;
+import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.enumerations.LogType;
+import cgeo.geocaching.geopoint.Geopoint;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
public class ExportTest extends CGeoTestCase {
@@ -16,4 +24,48 @@ public class ExportTest extends CGeoTestCase {
assertEquals("Non matching export " + logStr.toString(), "GCX1234,2012-11-18T13:20:20Z,Found it,\"Hidden in a tree\"\n", logStr.toString());
}
+ public static void testGpxExportSmilies() {
+ final Geocache cache = new Geocache();
+ cache.setGeocode("GCX1234");
+ cache.setCoords(new Geopoint("N 49 44.000 E 8 37.000"));
+ final LogEntry log = new LogEntry(1353244820000L, LogType.FOUND_IT, "Smile: \ud83d\ude0a");
+ cache.getLogs().add(log);
+ cgData.saveCache(cache, LoadFlags.SAVE_ALL);
+ ArrayList<Geocache> exportList = new ArrayList<Geocache>();
+ exportList.add(cache);
+ GpxExportTester gpxExport = new GpxExportTester();
+ File result = null;
+ try {
+ result = gpxExport.testExportSync(exportList);
+ } catch (InterruptedException e) {
+ fail(e.getCause().toString());
+ } catch (ExecutionException e) {
+ fail(e.getCause().toString());
+ } finally {
+ cgData.removeCache(cache.getGeocode(), LoadFlags.REMOVE_ALL);
+ }
+
+ assertNotNull(result);
+
+ result.delete();
+ }
+
+ private static class GpxExportTester extends GpxExport {
+
+ protected GpxExportTester() {
+ super();
+ }
+
+ public File testExportSync(List<Geocache> caches) throws InterruptedException, ExecutionException {
+ final ArrayList<String> geocodes = new ArrayList<String>(caches.size());
+ for (final Geocache cache: caches) {
+ geocodes.add(cache.getGeocode());
+ }
+ final ExportTask task = new ExportTask(null);
+ task.execute(geocodes.toArray(new String[geocodes.size()]));
+ return task.get();
+ }
+
+ }
+
}