| // 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)); |
| } |
| } |