diff options
Diffstat (limited to 'WordPress/src/main/java/org/xmlrpc/android/XMLRPCSerializer.java')
-rw-r--r-- | WordPress/src/main/java/org/xmlrpc/android/XMLRPCSerializer.java | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/xmlrpc/android/XMLRPCSerializer.java b/WordPress/src/main/java/org/xmlrpc/android/XMLRPCSerializer.java new file mode 100644 index 000000000..5840ea118 --- /dev/null +++ b/WordPress/src/main/java/org/xmlrpc/android/XMLRPCSerializer.java @@ -0,0 +1,298 @@ +package org.xmlrpc.android; + +import android.text.TextUtils; +import android.util.Base64; +import android.util.Xml; + +import org.wordpress.android.util.helpers.MediaFile; +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.StringUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SimpleTimeZone; + +class XMLRPCSerializer { + static final String TAG_NAME = "name"; + static final String TAG_MEMBER = "member"; + static final String TAG_VALUE = "value"; + static final String TAG_DATA = "data"; + + static final String TYPE_INT = "int"; + static final String TYPE_I4 = "i4"; + static final String TYPE_I8 = "i8"; + static final String TYPE_DOUBLE = "double"; + static final String TYPE_BOOLEAN = "boolean"; + static final String TYPE_STRING = "string"; + static final String TYPE_DATE_TIME_ISO8601 = "dateTime.iso8601"; + static final String TYPE_BASE64 = "base64"; + static final String TYPE_ARRAY = "array"; + static final String TYPE_STRUCT = "struct"; + + static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + static Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT")); + + private static final XmlSerializer serializeTester; + + static { + serializeTester = Xml.newSerializer(); + try { + serializeTester.setOutput(new NullOutputStream(), "UTF-8"); + } catch (IllegalArgumentException e) { + AppLog.e(AppLog.T.EDITOR, "IllegalArgumentException setting test serializer output stream", e ); + } catch (IllegalStateException e) { + AppLog.e(AppLog.T.EDITOR, "IllegalStateException setting test serializer output stream", e ); + } catch (IOException e) { + AppLog.e(AppLog.T.EDITOR, "IOException setting test serializer output stream", e ); + } + } + + @SuppressWarnings("unchecked") + static void serialize(XmlSerializer serializer, Object object) throws IOException { + // check for scalar types: + if (object instanceof Integer || object instanceof Short || object instanceof Byte) { + serializer.startTag(null, TYPE_I4).text(object.toString()).endTag(null, TYPE_I4); + } else + if (object instanceof Long) { + // Note Long should be represented by a TYPE_I8 but the WordPress end point doesn't support <i8> tag + // Long usually represents IDs, so we convert them to string + serializer.startTag(null, TYPE_STRING).text(object.toString()).endTag(null, TYPE_STRING); + AppLog.w(T.API, "long type could be misinterpreted when sent to the WordPress XMLRPC end point"); + } else + if (object instanceof Double || object instanceof Float) { + serializer.startTag(null, TYPE_DOUBLE).text(object.toString()).endTag(null, TYPE_DOUBLE); + } else + if (object instanceof Boolean) { + Boolean bool = (Boolean) object; + String boolStr = bool.booleanValue() ? "1" : "0"; + serializer.startTag(null, TYPE_BOOLEAN).text(boolStr).endTag(null, TYPE_BOOLEAN); + } else + if (object instanceof String) { + serializer.startTag(null, TYPE_STRING).text(makeValidInputString((String) object)).endTag(null, TYPE_STRING); + } else + if (object instanceof Date || object instanceof Calendar) { + Date date = (Date) object; + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + dateFormat.setCalendar(cal); + String sDate = dateFormat.format(date); + serializer.startTag(null, TYPE_DATE_TIME_ISO8601).text(sDate).endTag(null, TYPE_DATE_TIME_ISO8601); + } else + if (object instanceof byte[] ){ + String value; + try { + value = Base64.encodeToString((byte[])object, Base64.DEFAULT); + serializer.startTag(null, TYPE_BASE64).text(value).endTag(null, TYPE_BASE64); + } catch (OutOfMemoryError e) { + throw new IOException("Out of memory"); + } + } + else if( object instanceof MediaFile ) { + //convert media file binary to base64 + serializer.startTag( null, "base64" ); + MediaFile mediaFile = (MediaFile) object; + InputStream inStream = new DataInputStream(new FileInputStream(mediaFile.getFilePath())); + byte[] buffer = new byte[3600];//you must use a 24bit multiple + int length = -1; + String chunk = null; + while ((length = inStream.read(buffer)) > 0) { + chunk = Base64.encodeToString(buffer, 0, length, Base64.DEFAULT); + serializer.text(chunk); + } + inStream.close(); + serializer.endTag(null, "base64"); + }else + if (object instanceof List<?>) { + serializer.startTag(null, TYPE_ARRAY).startTag(null, TAG_DATA); + List<Object> list = (List<Object>) object; + Iterator<Object> iter = list.iterator(); + while (iter.hasNext()) { + Object o = iter.next(); + serializer.startTag(null, TAG_VALUE); + serialize(serializer, o); + serializer.endTag(null, TAG_VALUE); + } + serializer.endTag(null, TAG_DATA).endTag(null, TYPE_ARRAY); + } else + if (object instanceof Object[]) { + serializer.startTag(null, TYPE_ARRAY).startTag(null, TAG_DATA); + Object[] objects = (Object[]) object; + for (int i=0; i<objects.length; i++) { + Object o = objects[i]; + serializer.startTag(null, TAG_VALUE); + serialize(serializer, o); + serializer.endTag(null, TAG_VALUE); + } + serializer.endTag(null, TAG_DATA).endTag(null, TYPE_ARRAY); + } else + if (object instanceof Map) { + serializer.startTag(null, TYPE_STRUCT); + Map<String, Object> map = (Map<String, Object>) object; + Iterator<Entry<String, Object>> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Entry<String, Object> entry = iter.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + + serializer.startTag(null, TAG_MEMBER); + serializer.startTag(null, TAG_NAME).text(key).endTag(null, TAG_NAME); + serializer.startTag(null, TAG_VALUE); + serialize(serializer, value); + serializer.endTag(null, TAG_VALUE); + serializer.endTag(null, TAG_MEMBER); + } + serializer.endTag(null, TYPE_STRUCT); + } else { + throw new IOException("Cannot serialize " + object); + } + } + + private static final String makeValidInputString(final String input) throws IOException { + if (TextUtils.isEmpty(input)) + return ""; + + if (serializeTester == null) + return input; + + try { + // try to encode the string as-is, 99.9% of the time it's OK + serializeTester.text(input); + return input; + } catch (IllegalArgumentException e) { + // There are characters outside the XML unicode charset as specified by the XML 1.0 standard + // See http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char + AppLog.e(AppLog.T.EDITOR, "There are characters outside the XML unicode charset as specified by the XML 1.0 standard", e ); + } + + // We need to do the following things: + // 1. Replace surrogates with HTML Entity. + // 2. Replace emoji with their textual versions (if available on WP) + // 3. Try to serialize the resulting string. + // 4. If it fails again, strip characters that are not allowed in XML 1.0 + final String noEmojiString = StringUtils.replaceUnicodeSurrogateBlocksWithHTMLEntities(input); + try { + serializeTester.text(noEmojiString); + return noEmojiString; + } catch (IllegalArgumentException e) { + AppLog.e(AppLog.T.EDITOR, "noEmojiString still contains characters outside the XML unicode charset as specified by the XML 1.0 standard", e ); + return StringUtils.stripNonValidXMLCharacters(noEmojiString); + } + } + + static Object deserialize(XmlPullParser parser) throws XmlPullParserException, IOException, NumberFormatException { + parser.require(XmlPullParser.START_TAG, null, TAG_VALUE); + + parser.nextTag(); + String typeNodeName = parser.getName(); + + Object obj; + if (typeNodeName.equals(TYPE_INT) || typeNodeName.equals(TYPE_I4)) { + String value = parser.nextText(); + try { + obj = Integer.parseInt(value); + } catch (NumberFormatException nfe) { + AppLog.w(T.API, "Server replied with an invalid 4 bytes int value, trying to parse it as 8 bytes long"); + obj = Long.parseLong(value); + } + } else + if (typeNodeName.equals(TYPE_I8)) { + String value = parser.nextText(); + obj = Long.parseLong(value); + } else + if (typeNodeName.equals(TYPE_DOUBLE)) { + String value = parser.nextText(); + obj = Double.parseDouble(value); + } else + if (typeNodeName.equals(TYPE_BOOLEAN)) { + String value = parser.nextText(); + obj = value.equals("1") ? Boolean.TRUE : Boolean.FALSE; + } else + if (typeNodeName.equals(TYPE_STRING)) { + obj = parser.nextText(); + } else + if (typeNodeName.equals(TYPE_DATE_TIME_ISO8601)) { + dateFormat.setCalendar(cal); + String value = parser.nextText(); + try { + obj = dateFormat.parseObject(value); + } catch (ParseException e) { + AppLog.e(T.API, e); + obj = value; + } + } else + if (typeNodeName.equals(TYPE_BASE64)) { + String value = parser.nextText(); + BufferedReader reader = new BufferedReader(new StringReader(value)); + String line; + StringBuffer sb = new StringBuffer(); + while ((line = reader.readLine()) != null) { + sb.append(line); + } + obj = Base64.decode(sb.toString(), Base64.DEFAULT); + } else + if (typeNodeName.equals(TYPE_ARRAY)) { + parser.nextTag(); // TAG_DATA (<data>) + parser.require(XmlPullParser.START_TAG, null, TAG_DATA); + + parser.nextTag(); + List<Object> list = new ArrayList<Object>(); + while (parser.getName().equals(TAG_VALUE)) { + list.add(deserialize(parser)); + parser.nextTag(); + } + parser.require(XmlPullParser.END_TAG, null, TAG_DATA); + parser.nextTag(); // TAG_ARRAY (</array>) + parser.require(XmlPullParser.END_TAG, null, TYPE_ARRAY); + obj = list.toArray(); + } else + if (typeNodeName.equals(TYPE_STRUCT)) { + parser.nextTag(); + Map<String, Object> map = new HashMap<String, Object>(); + while (parser.getName().equals(TAG_MEMBER)) { + String memberName = null; + Object memberValue = null; + while (true) { + parser.nextTag(); + String name = parser.getName(); + if (name.equals(TAG_NAME)) { + memberName = parser.nextText(); + } else + if (name.equals(TAG_VALUE)) { + memberValue = deserialize(parser); + } else { + break; + } + } + if (memberName != null && memberValue != null) { + map.put(memberName, memberValue); + } + parser.require(XmlPullParser.END_TAG, null, TAG_MEMBER); + parser.nextTag(); + } + parser.require(XmlPullParser.END_TAG, null, TYPE_STRUCT); + obj = map; + } else { + throw new IOException("Cannot deserialize " + parser.getName()); + } + parser.nextTag(); // TAG_VALUE (</value>) + parser.require(XmlPullParser.END_TAG, null, TAG_VALUE); + return obj; + } +} |