blob: 42a25343fffba5b4221c33e65446e1106d7d70e5 [file] [log] [blame]
// Copyright 2007 The Android Open Source Project
package com.google.wireless.gdata.contacts.parser.xml;
import com.google.wireless.gdata.contacts.data.ContactEntry;
import com.google.wireless.gdata.contacts.data.ContactsElement;
import com.google.wireless.gdata.contacts.data.ContactsFeed;
import com.google.wireless.gdata.contacts.data.EmailAddress;
import com.google.wireless.gdata.contacts.data.ImAddress;
import com.google.wireless.gdata.contacts.data.Organization;
import com.google.wireless.gdata.contacts.data.PhoneNumber;
import com.google.wireless.gdata.contacts.data.PostalAddress;
import com.google.wireless.gdata.contacts.data.GroupMembershipInfo;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.data.Feed;
import com.google.wireless.gdata.data.XmlUtils;
import com.google.wireless.gdata.data.ExtendedProperty;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Enumeration;
/**
* GDataParser for a contacts feed.
*/
public class XmlContactsGDataParser extends XmlGDataParser {
/** Namespace prefix for Contacts */
public static final String NAMESPACE_CONTACTS = "gContact";
/** Namespace URI for Contacts */
public static final String NAMESPACE_CONTACTS_URI =
"http://schemas.google.com/contact/2008";
/** The photo link rels */
public static final String LINK_REL_PHOTO = "http://schemas.google.com/contacts/2008/rel#photo";
public static final String LINK_REL_EDIT_PHOTO =
"http://schemas.google.com/contacts/2008/rel#edit-photo";
/** The phone number type gdata string. */
private static final String GD_NAMESPACE = "http://schemas.google.com/g/2005#";
public static final String TYPESTRING_MOBILE = GD_NAMESPACE + "mobile";
public static final String TYPESTRING_HOME = GD_NAMESPACE + "home";
public static final String TYPESTRING_WORK = GD_NAMESPACE + "work";
public static final String TYPESTRING_HOME_FAX = GD_NAMESPACE + "home_fax";
public static final String TYPESTRING_WORK_FAX = GD_NAMESPACE + "work_fax";
public static final String TYPESTRING_PAGER = GD_NAMESPACE + "pager";
public static final String TYPESTRING_OTHER = GD_NAMESPACE + "other";
public static final String IM_PROTOCOL_AIM = GD_NAMESPACE + "AIM";
public static final String IM_PROTOCOL_MSN = GD_NAMESPACE + "MSN";
public static final String IM_PROTOCOL_YAHOO = GD_NAMESPACE + "YAHOO";
public static final String IM_PROTOCOL_SKYPE = GD_NAMESPACE + "SKYPE";
public static final String IM_PROTOCOL_QQ = GD_NAMESPACE + "QQ";
public static final String IM_PROTOCOL_GOOGLE_TALK = GD_NAMESPACE + "GOOGLE_TALK";
public static final String IM_PROTOCOL_ICQ = GD_NAMESPACE + "ICQ";
public static final String IM_PROTOCOL_JABBER = GD_NAMESPACE + "JABBER";
private static final Hashtable REL_TO_TYPE_EMAIL;
private static final Hashtable REL_TO_TYPE_PHONE;
private static final Hashtable REL_TO_TYPE_POSTAL;
private static final Hashtable REL_TO_TYPE_IM;
private static final Hashtable REL_TO_TYPE_ORGANIZATION;
private static final Hashtable IM_PROTOCOL_STRING_TO_TYPE_MAP;
public static final Hashtable TYPE_TO_REL_EMAIL;
public static final Hashtable TYPE_TO_REL_PHONE;
public static final Hashtable TYPE_TO_REL_POSTAL;
public static final Hashtable TYPE_TO_REL_IM;
public static final Hashtable TYPE_TO_REL_ORGANIZATION;
public static final Hashtable IM_PROTOCOL_TYPE_TO_STRING_MAP;
static {
Hashtable map;
map = new Hashtable();
map.put(TYPESTRING_HOME, new Byte(EmailAddress.TYPE_HOME));
map.put(TYPESTRING_WORK, new Byte(EmailAddress.TYPE_WORK));
map.put(TYPESTRING_OTHER, new Byte(EmailAddress.TYPE_OTHER));
// TODO: this is a hack to support the old feed
map.put(GD_NAMESPACE + "primary", (byte)4);
REL_TO_TYPE_EMAIL = map;
TYPE_TO_REL_EMAIL = swapMap(map);
map = new Hashtable();
map.put(TYPESTRING_HOME, new Byte(PhoneNumber.TYPE_HOME));
map.put(TYPESTRING_MOBILE, new Byte(PhoneNumber.TYPE_MOBILE));
map.put(TYPESTRING_PAGER, new Byte(PhoneNumber.TYPE_PAGER));
map.put(TYPESTRING_WORK, new Byte(PhoneNumber.TYPE_WORK));
map.put(TYPESTRING_HOME_FAX, new Byte(PhoneNumber.TYPE_HOME_FAX));
map.put(TYPESTRING_WORK_FAX, new Byte(PhoneNumber.TYPE_WORK_FAX));
map.put(TYPESTRING_OTHER, new Byte(PhoneNumber.TYPE_OTHER));
REL_TO_TYPE_PHONE = map;
TYPE_TO_REL_PHONE = swapMap(map);
map = new Hashtable();
map.put(TYPESTRING_HOME, new Byte(PostalAddress.TYPE_HOME));
map.put(TYPESTRING_WORK, new Byte(PostalAddress.TYPE_WORK));
map.put(TYPESTRING_OTHER, new Byte(PostalAddress.TYPE_OTHER));
REL_TO_TYPE_POSTAL = map;
TYPE_TO_REL_POSTAL = swapMap(map);
map = new Hashtable();
map.put(TYPESTRING_HOME, new Byte(ImAddress.TYPE_HOME));
map.put(TYPESTRING_WORK, new Byte(ImAddress.TYPE_WORK));
map.put(TYPESTRING_OTHER, new Byte(ImAddress.TYPE_OTHER));
REL_TO_TYPE_IM = map;
TYPE_TO_REL_IM = swapMap(map);
map = new Hashtable();
map.put(TYPESTRING_WORK, new Byte(Organization.TYPE_WORK));
map.put(TYPESTRING_OTHER, new Byte(Organization.TYPE_OTHER));
REL_TO_TYPE_ORGANIZATION = map;
TYPE_TO_REL_ORGANIZATION = swapMap(map);
map = new Hashtable();
map.put(IM_PROTOCOL_AIM, new Byte(ImAddress.PROTOCOL_AIM));
map.put(IM_PROTOCOL_MSN, new Byte(ImAddress.PROTOCOL_MSN));
map.put(IM_PROTOCOL_YAHOO, new Byte(ImAddress.PROTOCOL_YAHOO));
map.put(IM_PROTOCOL_SKYPE, new Byte(ImAddress.PROTOCOL_SKYPE));
map.put(IM_PROTOCOL_QQ, new Byte(ImAddress.PROTOCOL_QQ));
map.put(IM_PROTOCOL_GOOGLE_TALK, new Byte(ImAddress.PROTOCOL_GOOGLE_TALK));
map.put(IM_PROTOCOL_ICQ, new Byte(ImAddress.PROTOCOL_ICQ));
map.put(IM_PROTOCOL_JABBER, new Byte(ImAddress.PROTOCOL_JABBER));
IM_PROTOCOL_STRING_TO_TYPE_MAP = map;
IM_PROTOCOL_TYPE_TO_STRING_MAP = swapMap(map);
}
private static Hashtable swapMap(Hashtable originalMap) {
Hashtable newMap = new Hashtable();
Enumeration enumeration = originalMap.keys();
while (enumeration.hasMoreElements()) {
Object key = enumeration.nextElement();
Object value = originalMap.get(key);
if (newMap.containsKey(value)) {
throw new IllegalArgumentException("value " + value
+ " was already encountered");
}
newMap.put(value, key);
}
return newMap;
}
/**
* Creates a new XmlEventsGDataParser.
* @param is The InputStream that should be parsed.
* @throws ParseException Thrown if a parser cannot be created.
*/
public XmlContactsGDataParser(InputStream is, XmlPullParser parser)
throws ParseException {
super(is, parser);
}
/*
* (non-Javadoc)
* @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createFeed()
*/
protected Feed createFeed() {
return new ContactsFeed();
}
/*
* (non-Javadoc)
* @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createEntry()
*/
protected Entry createEntry() {
return new ContactEntry();
}
protected void handleExtraElementInEntry(Entry entry) throws XmlPullParserException, IOException {
XmlPullParser parser = getParser();
if (!(entry instanceof ContactEntry)) {
throw new IllegalArgumentException("Expected ContactEntry!");
}
ContactEntry contactEntry = (ContactEntry) entry;
String name = parser.getName();
if ("email".equals(name)) {
EmailAddress emailAddress = new EmailAddress();
parseContactsElement(emailAddress, parser, REL_TO_TYPE_EMAIL);
// TODO: remove this when the feed is upgraded
if (emailAddress.getType() == 4) {
emailAddress.setType(EmailAddress.TYPE_OTHER);
emailAddress.setIsPrimary(true);
emailAddress.setLabel(null);
}
emailAddress.setAddress(parser.getAttributeValue(null /* ns */, "address"));
contactEntry.addEmailAddress(emailAddress);
} else if ("deleted".equals(name)) {
contactEntry.setDeleted(true);
} else if ("im".equals(name)) {
ImAddress imAddress = new ImAddress();
parseContactsElement(imAddress, parser, REL_TO_TYPE_IM);
imAddress.setAddress(parser.getAttributeValue(null /* ns */, "address"));
imAddress.setLabel(parser.getAttributeValue(null /* ns */, "label"));
String protocolString = parser.getAttributeValue(null /* ns */, "protocol");
if (protocolString == null) {
imAddress.setProtocolPredefined(ImAddress.PROTOCOL_NONE);
imAddress.setProtocolCustom(null);
} else {
Byte predefinedProtocol = (Byte) IM_PROTOCOL_STRING_TO_TYPE_MAP.get(protocolString);
if (predefinedProtocol == null) {
imAddress.setProtocolPredefined(ImAddress.PROTOCOL_CUSTOM);
imAddress.setProtocolCustom(protocolString);
} else {
imAddress.setProtocolPredefined(predefinedProtocol.byteValue());
imAddress.setProtocolCustom(null);
}
}
contactEntry.addImAddress(imAddress);
} else if ("postalAddress".equals(name)) {
PostalAddress postalAddress = new PostalAddress();
parseContactsElement(postalAddress, parser, REL_TO_TYPE_POSTAL);
postalAddress.setValue(XmlUtils.extractChildText(parser));
contactEntry.addPostalAddress(postalAddress);
} else if ("phoneNumber".equals(name)) {
PhoneNumber phoneNumber = new PhoneNumber();
parseContactsElement(phoneNumber, parser, REL_TO_TYPE_PHONE);
phoneNumber.setPhoneNumber(XmlUtils.extractChildText(parser));
contactEntry.addPhoneNumber(phoneNumber);
} else if ("organization".equals(name)) {
Organization organization = new Organization();
parseContactsElement(organization, parser, REL_TO_TYPE_ORGANIZATION);
handleOrganizationSubElement(organization, parser);
contactEntry.addOrganization(organization);
} else if ("extendedProperty".equals(name)) {
ExtendedProperty extendedProperty = new ExtendedProperty();
parseExtendedProperty(extendedProperty);
contactEntry.addExtendedProperty(extendedProperty);
} else if ("groupMembershipInfo".equals(name)) {
GroupMembershipInfo group = new GroupMembershipInfo();
group.setGroup(parser.getAttributeValue(null /* ns */, "href"));
group.setDeleted("true".equals(parser.getAttributeValue(null /* ns */, "deleted")));
contactEntry.addGroup(group);
} else if ("yomiName".equals(name)) {
String yomiName = XmlUtils.extractChildText(parser);
contactEntry.setYomiName(yomiName);
}
}
@Override
protected void handleExtraLinkInEntry(String rel, String type, String href, Entry entry)
throws XmlPullParserException, IOException {
if (LINK_REL_PHOTO.equals(rel)) {
ContactEntry contactEntry = (ContactEntry) entry;
contactEntry.setLinkPhoto(href, type);
} else if (LINK_REL_EDIT_PHOTO.equals(rel)) {
ContactEntry contactEntry = (ContactEntry) entry;
contactEntry.setLinkEditPhoto(href, type);
}
}
private static void parseContactsElement(ContactsElement element, XmlPullParser parser,
Hashtable relToTypeMap) throws XmlPullParserException {
String rel = parser.getAttributeValue(null /* ns */, "rel");
String label = parser.getAttributeValue(null /* ns */, "label");
if ((label == null && rel == null) || (label != null && rel != null)) {
// TODO: remove this once the focus feed is fixed to not send this case
rel = TYPESTRING_OTHER;
}
if (rel != null) {
final Object type = relToTypeMap.get(rel.toLowerCase());
if (type == null) {
throw new XmlPullParserException("unknown rel, " + rel);
}
element.setType(((Byte) type).byteValue());
}
element.setLabel(label);
element.setIsPrimary("true".equals(parser.getAttributeValue(null /* ns */, "primary")));
}
private static void handleOrganizationSubElement(Organization element, XmlPullParser parser)
throws XmlPullParserException, IOException {
int depth = parser.getDepth();
while (true) {
String tag = XmlUtils.nextDirectChildTag(parser, depth);
if (tag == null) break;
if ("orgName".equals(tag)) {
element.setName(XmlUtils.extractChildText(parser));
} else if ("orgTitle".equals(tag)) {
element.setTitle(XmlUtils.extractChildText(parser));
}
}
}
/**
* Parse the ExtendedProperty. The parser is assumed to be at the beginning of the tag
* for the ExtendedProperty.
* @param extendedProperty the ExtendedProperty object to populate
*/
private void parseExtendedProperty(ExtendedProperty extendedProperty)
throws IOException, XmlPullParserException {
XmlPullParser parser = getParser();
extendedProperty.setName(parser.getAttributeValue(null /* ns */, "name"));
extendedProperty.setValue(parser.getAttributeValue(null /* ns */, "value"));
extendedProperty.setXmlBlob(XmlUtils.extractFirstChildTextIgnoreRest(parser));
}
}