From: <my...@us...> - 2009-02-26 03:22:37
|
Revision: 1750 http://aperture.svn.sourceforge.net/aperture/?rev=1750&view=rev Author: mylka Date: 2009-02-26 01:54:26 +0000 (Thu, 26 Feb 2009) Log Message: ----------- 2635213 - replaced jpim with ical4j-vcard. I had to submit some patches before we could use it, but I'm sure this project will be much more active than jpim, which has been dead for 3 years. Modified Paths: -------------- trunk/aperture/.classpath trunk/aperture/selectors.xml trunk/aperture/src/java/org/semanticdesktop/aperture/subcrawler/vcard/VcardSubCrawler.java trunk/aperture/src/test/org/semanticdesktop/aperture/docs/vcard-antoni-kontact.vcf Added Paths: ----------- trunk/aperture/lib/ical4j-1.0-rc1.jar trunk/aperture/lib/ical4j-vcard-0.9.1-SNAPSHOT.jar trunk/aperture/lib/ical4j-vcard-license.txt Removed Paths: ------------- trunk/aperture/lib/ical4j-1.0-beta4.jar trunk/aperture/lib/jpim-0.1-aperture-1.jar trunk/aperture/lib/jpim-license.txt trunk/aperture/patches/jpim-0.1-aperture-1/ Modified: trunk/aperture/.classpath =================================================================== --- trunk/aperture/.classpath 2009-02-25 13:26:01 UTC (rev 1749) +++ trunk/aperture/.classpath 2009-02-26 01:54:26 UTC (rev 1750) @@ -12,11 +12,9 @@ <classpathentry exported="true" kind="lib" path="lib/nrlvalidator-0.1.jar"/> <classpathentry exported="true" kind="lib" path="lib/unionsail-0.1.jar"/> <classpathentry exported="true" kind="lib" path="lib/flickrapi-1.0.jar"/> - <classpathentry exported="true" kind="lib" path="lib/ical4j-1.0-beta4.jar"/> <classpathentry exported="true" kind="lib" path="lib/commons-lang-2.3.jar"/> <classpathentry exported="true" kind="lib" path="lib/jacob-1.10.jar"/> <classpathentry exported="true" kind="lib" path="lib/jaudiotagger-1.0.8.jar"/> - <classpathentry exported="true" kind="lib" path="lib/jpim-0.1-aperture-1.jar"/> <classpathentry exported="true" kind="lib" path="lib/mstor-0.9.11.jar"/> <classpathentry exported="true" kind="lib" path="lib/commons-codec-1.3.jar"/> <classpathentry exported="true" kind="lib" path="lib/commons-httpclient-3.1.jar"/> @@ -59,5 +57,8 @@ <classpathentry exported="true" kind="lib" path="lib/slf4j-api-1.5.0.jar"/> <classpathentry exported="true" kind="lib" path="lib/slf4j-jdk14-1.5.0.jar"/> <classpathentry exported="true" kind="lib" path="lib/org.eclipse.osgi_3.4.0.v20080605-1900.jar"/> + <classpathentry kind="lib" path="lib/ical4j-1.0-rc1.jar"/> + <classpathentry kind="lib" path="lib/ical4j-vcard-0.9.1-SNAPSHOT.jar"/> + <classpathentry combineaccessrules="false" kind="src" path="/ical4j-vcard"/> <classpathentry kind="output" path="build_eclipse"/> </classpath> Deleted: trunk/aperture/lib/ical4j-1.0-beta4.jar =================================================================== (Binary files differ) Added: trunk/aperture/lib/ical4j-1.0-rc1.jar =================================================================== (Binary files differ) Property changes on: trunk/aperture/lib/ical4j-1.0-rc1.jar ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Added: trunk/aperture/lib/ical4j-vcard-0.9.1-SNAPSHOT.jar =================================================================== (Binary files differ) Property changes on: trunk/aperture/lib/ical4j-vcard-0.9.1-SNAPSHOT.jar ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Added: trunk/aperture/lib/ical4j-vcard-license.txt =================================================================== --- trunk/aperture/lib/ical4j-vcard-license.txt (rev 0) +++ trunk/aperture/lib/ical4j-vcard-license.txt 2009-02-26 01:54:26 UTC (rev 1750) @@ -0,0 +1,33 @@ +================== + iCal4j - License +================== + +Copyright (c) 2008, Ben Fortuna +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + o Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + + o Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + + o Neither the name of Ben Fortuna nor the names of any other contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Property changes on: trunk/aperture/lib/ical4j-vcard-license.txt ___________________________________________________________________ Added: svn:mime-type + text/plain Deleted: trunk/aperture/lib/jpim-0.1-aperture-1.jar =================================================================== (Binary files differ) Deleted: trunk/aperture/lib/jpim-license.txt =================================================================== --- trunk/aperture/lib/jpim-license.txt 2009-02-25 13:26:01 UTC (rev 1749) +++ trunk/aperture/lib/jpim-license.txt 2009-02-26 01:54:26 UTC (rev 1750) @@ -1,30 +0,0 @@ -Java PIM Library (jpim) -Copyright (c) 2001-2003 jpim team. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -Neither the name of the author nor the names of its contributors -may be used to endorse or promote products derived from this software -without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS -IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. Modified: trunk/aperture/selectors.xml =================================================================== --- trunk/aperture/selectors.xml 2009-02-25 13:26:01 UTC (rev 1749) +++ trunk/aperture/selectors.xml 2009-02-26 01:54:26 UTC (rev 1750) @@ -252,7 +252,7 @@ <or> <filename name="org/semanticdesktop/aperture/crawler/ical/*" /> <filename name="org/semanticdesktop/aperture/datasource/ical/*" /> - <filename name="lib/ical4j-1.0-beta4.jar" /> + <filename name="lib/ical4j-1.0-rc1.jar" /> <filename name="lib/commons-lang-2.3.jar" /> <filename name="lib/commons-codec-1.3.jar" /> </or> @@ -516,7 +516,8 @@ <and> <or> <filename name="org/semanticdesktop/aperture/subcrawler/vcard/*" /> - <filename name="lib/jpim-0.1-aperture-1.jar" /> + <filename name="lib/ical4j-vcard-0.9.1-SNAPSHOT.jar" /> + <filename name="lib/ical4j-1.0-rc1.jar" /> </or> <selector refid="bundle.basic.selector" /> </and> Modified: trunk/aperture/src/java/org/semanticdesktop/aperture/subcrawler/vcard/VcardSubCrawler.java =================================================================== --- trunk/aperture/src/java/org/semanticdesktop/aperture/subcrawler/vcard/VcardSubCrawler.java 2009-02-25 13:26:01 UTC (rev 1749) +++ trunk/aperture/src/java/org/semanticdesktop/aperture/subcrawler/vcard/VcardSubCrawler.java 2009-02-26 01:54:26 UTC (rev 1750) @@ -7,28 +7,41 @@ package org.semanticdesktop.aperture.subcrawler.vcard; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.nio.charset.Charset; -import java.text.ParseException; import java.util.Date; +import java.util.List; -import net.wimpi.pim.Pim; -import net.wimpi.pim.contact.io.ContactMarshaller; -import net.wimpi.pim.contact.io.ContactUnmarshaller; -import net.wimpi.pim.contact.model.Address; -import net.wimpi.pim.contact.model.Communications; -import net.wimpi.pim.contact.model.Contact; -import net.wimpi.pim.contact.model.EmailAddress; -import net.wimpi.pim.contact.model.GeographicalInformation; -import net.wimpi.pim.contact.model.Image; -import net.wimpi.pim.contact.model.Key; -import net.wimpi.pim.contact.model.Organization; -import net.wimpi.pim.contact.model.OrganizationalIdentity; -import net.wimpi.pim.contact.model.PersonalIdentity; -import net.wimpi.pim.contact.model.PhoneNumber; -import net.wimpi.pim.contact.model.Sound; -import net.wimpi.pim.factory.ContactIOFactory; +import net.fortuna.ical4j.model.ValidationException; +import net.fortuna.ical4j.vcard.GroupRegistry; +import net.fortuna.ical4j.vcard.Parameter; +import net.fortuna.ical4j.vcard.ParameterFactory; +import net.fortuna.ical4j.vcard.ParameterFactoryRegistry; +import net.fortuna.ical4j.vcard.Property; +import net.fortuna.ical4j.vcard.PropertyFactoryRegistry; +import net.fortuna.ical4j.vcard.VCard; +import net.fortuna.ical4j.vcard.VCardBuilder; +import net.fortuna.ical4j.vcard.VCardOutputter; +import net.fortuna.ical4j.vcard.Property.Id; +import net.fortuna.ical4j.vcard.parameter.Encoding; +import net.fortuna.ical4j.vcard.parameter.Type; +import net.fortuna.ical4j.vcard.property.Address; +import net.fortuna.ical4j.vcard.property.BDay; +import net.fortuna.ical4j.vcard.property.Email; +import net.fortuna.ical4j.vcard.property.Geo; +import net.fortuna.ical4j.vcard.property.Logo; +import net.fortuna.ical4j.vcard.property.N; +import net.fortuna.ical4j.vcard.property.Nickname; +import net.fortuna.ical4j.vcard.property.Org; +import net.fortuna.ical4j.vcard.property.Photo; +import net.fortuna.ical4j.vcard.property.Revision; +import net.fortuna.ical4j.vcard.property.Telephone; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.net.QuotedPrintableCodec; import org.ontoware.rdf2go.model.Model; import org.ontoware.rdf2go.model.node.Resource; import org.ontoware.rdf2go.model.node.URI; @@ -53,6 +66,8 @@ import org.semanticdesktop.aperture.vocabulary.NEXIF; import org.semanticdesktop.aperture.vocabulary.NFO; import org.semanticdesktop.aperture.vocabulary.NIE; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An Extractor Implementation working with VCard documents. @@ -102,6 +117,8 @@ private static final String OBJECT_HASH_KEY = "contactHash"; + private Logger logger = LoggerFactory.getLogger(this.getClass()); + /** * @see SubCrawler#subCrawl(URI, InputStream, SubCrawlerHandler, DataSource, AccessData, Charset, String, RDFContainer) */ @@ -116,19 +133,21 @@ } try { - ContactIOFactory factory = Pim.getContactIOFactory(); - ContactUnmarshaller unmarshaller = factory.createContactUnmarshaller(); - ContactMarshaller marshaller = factory.createContactMarshaller(); - Contact[] contacts = unmarshaller.unmarshallContacts(stream); - if (contacts.length == 0) { - // nothing to be done, the file doesn't contain any contacts - return; + Reader reader = new InputStreamReader(stream); + GroupRegistry groupRegistry = new GroupRegistry(); + PropertyFactoryRegistry propReg = new PropertyFactoryRegistry(); + ParameterFactoryRegistry parReg = new ParameterFactoryRegistry(); + + addTypeParamsToRegistry(parReg); + + VCardBuilder builder = new VCardBuilder(reader,groupRegistry,propReg,parReg); + List<VCard> cards = builder.buildAll(); + VCardOutputter outputter = new VCardOutputter(false); + if (cards.size() == 1) { + processContact(cards.get(0), parentMetadata.getModel(), parentMetadata.getDescribedUri()); } - else if (contacts.length == 1) { - processContact(contacts[0], parentMetadata.getModel(), parentMetadata.getDescribedUri()); - } else { - processAddressBook(contacts, parentMetadata, handler, marshaller, accessData, dataSource); + processAddressBook(cards, parentMetadata, handler, outputter, accessData, dataSource); } } catch (Exception e) { @@ -136,6 +155,23 @@ } } + private void addTypeParamsToRegistry(ParameterFactoryRegistry parReg) { + for (final String name : new String[] {"HOME","WORK","MSG","PREF","VOICE","FAX","CELL", + "VIDEO","PAGER","BBS","MODEM","CAR","ISDN","PCS","INTERNET","X400","DOM", + "INTL","POSTAL","PARCEL"}) { + parReg.register(name, new ParameterFactory<Parameter>() { + public Parameter createParameter(String value) { + return new Type(name); + } + }); + parReg.register(name.toLowerCase(), new ParameterFactory<Parameter>() { + public Parameter createParameter(String value) { + return new Type(name); + } + }); + } + } + /** * @see SubCrawler#stopSubCrawler() */ @@ -148,18 +184,22 @@ return VcardSubCrawlerFactory.VCARD_URI_PREFIX; } - private void processAddressBook(Contact[] contacts, RDFContainer parentMetadata, - SubCrawlerHandler handler, ContactMarshaller marshaller, AccessData accessData, DataSource source) { + private void processAddressBook(List<VCard> contacts, RDFContainer parentMetadata, + SubCrawlerHandler handler, VCardOutputter out, AccessData accessData, DataSource source) { parentMetadata.add(RDF.type, NCO.ContactList); - for (Contact contact : contacts) { - String contactHash = getContactHash(contact, marshaller); - URI contactUri = generateURIForContact(contact, parentMetadata, contactHash); - RDFContainerFactory factory = handler.getRDFContainerFactory(contactUri.toString()); - RDFContainer container = factory.getRDFContainer(contactUri); - processContact(contact, container.getModel(), contactUri); - parentMetadata.add(NCO.containsContact, contactUri); - container.add(RDF.type, NCO.ContactListDataObject); - passMetadataToHandler(container, handler, contactHash, accessData, source); + for (VCard contact : contacts) { + try { + String contactHash = getContactHash(contact, out); + URI contactUri = generateURIForContact(contact, parentMetadata, contactHash); + RDFContainerFactory factory = handler.getRDFContainerFactory(contactUri.toString()); + RDFContainer container = factory.getRDFContainer(contactUri); + processContact(contact, container.getModel(), contactUri); + parentMetadata.add(NCO.containsContact, contactUri); + container.add(RDF.type, NCO.ContactListDataObject); + passMetadataToHandler(container, handler, contactHash, accessData, source); + } catch (Exception e) { + logger.warn("Failed to process vcard",e); + } } } @@ -183,57 +223,69 @@ } } - private void processContact(Contact contact, Model model, Resource contactResource) { + private void processContact(VCard contact, Model model, Resource contactResource) { model.addStatement(contactResource, RDF.type, NCO.Contact); - processPersonalIdentity(contact.getPersonalIdentity(), model, contactResource); - Resource affiliationResource = processOrganizationIdentity(contact.getOrganizationalIdentity(), - model, contactResource); + processPersonalIdentity(contact, model, contactResource); + Resource affiliationResource = processOrganizationIdentity(contact, model, contactResource); processCommonProperties(contact, model, contactResource, affiliationResource); } - private void processPersonalIdentity(PersonalIdentity personalIdentity, Model model, + private void processPersonalIdentity(VCard vc, Model model, Resource contactResource) { - if (personalIdentity == null) { - return; - } + // this property is present in all contacts, regardless of whether they are PersonContacts // or OrganizationContacts, the presence of this property cannot tell us anything interesting - addStringProperty(model, contactResource, NCO.fullname, personalIdentity.getFormattedName()); + addStringProperty(model, contactResource, NCO.fullname, getPropertyValue(vc,Id.FN)); // but the presence of the properties below is a different thing. // first we may check if any one of them is present and insert an appropriate type - if (personalIdentity.getAdditionalNameCount() > 0 || personalIdentity.getBirthDate() != null - || personalIdentity.getFirstname() != null || personalIdentity.getLastname() != null - || personalIdentity.getNicknameCount() > 0 || personalIdentity.listPrefixes().length > 0 - || personalIdentity.listSuffixes().length > 0) { + N name = (N)vc.getProperty(Id.N); + if (name != null && (!empty(name.getAdditionalNames()) || vc.getProperty(Id.BDAY) != null + || name.getGivenName() != null || name.getFamilyName() != null + || !empty(name.getPrefixes()) + || !empty(name.getSuffixes()))) { model.addStatement(contactResource, RDF.type, NCO.PersonContact); } + + if (name == null) { + return; + } - for (int i = 0; i < personalIdentity.getAdditionalNameCount(); i++) { - model.addStatement(contactResource, NCO.nameAdditional, personalIdentity.getAdditionalName(i)); + for (int i = 0; i < length(name.getAdditionalNames()); i++) { + String addName = name.getAdditionalNames()[i]; + model.addStatement(contactResource, NCO.nameAdditional, addName); } - addDateProperty(model, contactResource, NCO.birthDate, personalIdentity.getBirthDate()); - addStringProperty(model, contactResource, NCO.nameGiven, personalIdentity.getFirstname()); - for (int i = 0; i < personalIdentity.getAdditionalNameCount(); i++) { - model.addStatement(contactResource, NCO.nameAdditional, personalIdentity.getAdditionalName(i)); + BDay bday = (BDay)vc.getProperty(Id.BDAY); + if (bday != null) { + addDateProperty(model, contactResource, NCO.birthDate, bday.getDate()); } - addStringProperty(model, contactResource, NCO.nameFamily, personalIdentity.getLastname()); - for (int i = 0; i < personalIdentity.getNicknameCount(); i++) { - model.addStatement(contactResource, NCO.nickname, personalIdentity.getNickname(i)); + addStringProperty(model, contactResource, NCO.nameGiven, name.getGivenName()); + addStringProperty(model, contactResource, NCO.nameFamily, name.getFamilyName()); + Nickname nickname = (Nickname)vc.getProperty(Id.NICKNAME); + if (nickname != null) { + for (int i = 0; i < length(nickname.getNames()); i++) { + model.addStatement(contactResource, NCO.nickname, nickname.getNames()[i]); + } } - processImage(model, contactResource, NCO.photo, personalIdentity.getPhoto()); - for (String prefix : personalIdentity.listPrefixes()) { + Photo photo = (Photo)vc.getProperty(Id.PHOTO); + if (photo != null) { + processImage(model, contactResource, NCO.photo, + getParameterValue(photo, net.fortuna.ical4j.vcard.Parameter.Id.TYPE)); + } + for (int i = 0; i < length(name.getPrefixes()); i++) { + String prefix = name.getPrefixes()[i]; addStringProperty(model, contactResource, NCO.nameHonorificPrefix, prefix); } - for (String prefix : personalIdentity.listSuffixes()) { - addStringProperty(model, contactResource, NCO.nameHonorificSuffix, prefix); + for (int i = 0; i < length(name.getSuffixes()); i++) { + String suffix = name.getSuffixes()[i]; + addStringProperty(model, contactResource, NCO.nameHonorificSuffix, suffix); } } - private Resource processOrganizationIdentity(OrganizationalIdentity organizationalIdentity, Model model, + private Resource processOrganizationIdentity(VCard organizationalIdentity, Model model, Resource contactResource) { // first some sanity checking @@ -242,8 +294,10 @@ } // The question is, how to determine if the OrganizatonalIdentity is non-empty.. - if (organizationalIdentity.getAgent() == null && organizationalIdentity.getOrganization() == null - && organizationalIdentity.getRole() == null && organizationalIdentity.getTitle() == null) { + if (organizationalIdentity.getProperty(Id.AGENT) == null && + organizationalIdentity.getProperty(Id.ORG) == null && + organizationalIdentity.getProperty(Id.ROLE) == null && + organizationalIdentity.getProperty(Id.TITLE) == null) { return null; } // let's hope this sanity-check is enough, that's the price you pay for a higher-level API @@ -255,12 +309,13 @@ Resource affiliationResource = UriUtil.generateRandomResource(model); model.addStatement(contactResource, NCO.hasAffiliation, affiliationResource); model.addStatement(affiliationResource, RDF.type, NCO.Affiliation); - addStringProperty(model, affiliationResource, NCO.role, organizationalIdentity.getRole()); - addStringProperty(model, affiliationResource, NCO.title, organizationalIdentity.getTitle()); + addStringProperty(model, affiliationResource, NCO.role, getPropertyValue(organizationalIdentity, Id.ROLE)); + addStringProperty(model, affiliationResource, NCO.title, getPropertyValue(organizationalIdentity, Id.TITLE)); // so, the AGENT and ORGANIZATION both require an organizationResource, but we don't need to // create it if there are none (is it possible in VCARD at all?) - if (organizationalIdentity.getAgent() == null && organizationalIdentity.getOrganization() == null) { + if (organizationalIdentity.getProperty(Id.AGENT) == null && + organizationalIdentity.getProperty(Id.ORG) == null) { return affiliationResource; } // now we know we have to create an organization resource @@ -269,52 +324,67 @@ model.addStatement(organizationResource, RDF.type, NCO.OrganizationContact); model.addStatement(affiliationResource, NCO.org, organizationResource); - Organization organization = organizationalIdentity.getOrganization(); - if (organization != null) { - addStringProperty(model, organizationResource, NCO.fullname, organization.getName()); - processImage(model, organizationResource, NCO.logo, organization.getLogo()); - for (int i = 0; i < organization.getUnitCount(); i++) { + + Logo logo = (Logo)organizationalIdentity.getProperty(Id.LOGO); + if (logo != null) { + processImage(model, organizationResource, NCO.logo, + getParameterValue(logo, net.fortuna.ical4j.vcard.Parameter.Id.TYPE)); + } + + Org org = (Org) organizationalIdentity.getProperty(Id.ORG); + if (org != null) { + String[] vals = org.getValues(); + if (length(vals) >= 1) { + addStringProperty(model, organizationResource, NCO.fullname, vals[0]); + } + for (int i = 1; i < length(vals); i++) { // note that the departments are attached to the affiliationResource - addStringProperty(model, affiliationResource, NCO.department, organization.getUnit(i)); + addStringProperty(model, affiliationResource, NCO.department, vals[i]); } } return affiliationResource; } - private void processCommonProperties(Contact contact, Model model, Resource contactResource, + private void processCommonProperties(VCard contact, Model model, Resource contactResource, Resource affiliationResource) { // so, first the addresses - Address preferredAddress = contact.getPreferredAddress(); - for (Address address : contact.listAddresses()) { + List<Property> adrs = contact.getProperties(Id.ADR); + + for (Property address : adrs) { // let's hope this simple comparison will work as desired... - if (preferredAddress == address) { - processAddress(model, address, contactResource, affiliationResource, true); + String type = getParameterValue(address, net.fortuna.ical4j.vcard.Parameter.Id.TYPE); + if (type != null && type.contains("pref")) { + processAddress(model, (Address)address, contactResource, affiliationResource, true); } else { - processAddress(model, address, contactResource, affiliationResource, false); + processAddress(model, (Address)address, contactResource, affiliationResource, false); } } // then the complex properties - processCommunications(model, contact.getCommunications(), contactResource, affiliationResource); - processGeographicalInformation(model, contactResource, NCO.hasLocation, contact - .getGeographicalInformation()); - processPublicKey(model, contactResource, NCO.key, contact.getPublicKey()); - processSound(model, contactResource, NCO.sound, contact.getSound()); + processCommunications(model, contact, contactResource, affiliationResource); + processGeographicalInformation(model, contactResource, NCO.hasLocation, contact); + Property key = contact.getProperty(Id.KEY); + if (key != null) { + processPublicKey(model, contactResource, NCO.key, + getParameterValue(key, net.fortuna.ical4j.vcard.Parameter.Id.TYPE)); + } + Property sound = contact.getProperty(Id.SOUND); + if (sound != null) { + processSound(model, contactResource, NCO.sound, + getParameterValue(sound, net.fortuna.ical4j.vcard.Parameter.Id.TYPE)); + } // and then the simple properties - addStringProperty(model, contactResource, NCO.contactUID, contact.getUID()); - addUriProperty(model, contactResource, NCO.url, contact.getURL()); - addStringProperty(model, contactResource, NCO.note, contact.getNote()); - addDateTimeProperty(model, contactResource, NIE.contentLastModified, contact.getCurrentRevisionDate()); - - // and a list of the getters of Contact that are not supported by NCO - // contact.getAccessClassification() - // contact.listCategories(); - // contact.getExtensions(); - // contact.getLastAddedAddress(); - // contact.getPreferredAddress(); - covered by processAddress, no need to cover it here + addStringProperty(model, contactResource, NCO.contactUID, getPropertyValue(contact, Id.UID)); + addUriProperty(model, contactResource, NCO.url, getPropertyValue(contact, Id.URL)); + addStringProperty(model, contactResource, NCO.note, getPropertyValue(contact, Id.NOTE)); + + Revision rev = (Revision)contact.getProperty(Id.REV); + if (rev != null) { + addDateTimeProperty(model, contactResource, NIE.contentLastModified, rev.getDate()); + } } private void processAddress(Model model, Address address, Resource contactResource, @@ -322,10 +392,10 @@ if (address != null) { Resource addressResource = UriUtil.generateRandomResource(model); model.addStatement(addressResource, RDF.type, NCO.PostalAddress); - addStringProperty(model, addressResource, NCO.locality, address.getCity()); + addStringProperty(model, addressResource, NCO.locality, address.getLocality()); addStringProperty(model, addressResource, NCO.country, address.getCountry()); - addStringProperty(model, addressResource, NCO.postalcode, address.getPostalCode()); - addStringProperty(model, addressResource, NCO.pobox, address.getPostBox()); + addStringProperty(model, addressResource, NCO.postalcode, address.getPostcode()); + addStringProperty(model, addressResource, NCO.pobox, address.getPoBox()); addStringProperty(model, addressResource, NCO.region, address.getRegion()); addStringProperty(model, addressResource, NCO.streetAddress, address.getStreet()); addStringProperty(model, addressResource, NCO.extendedAddress, address.getExtended()); @@ -341,30 +411,34 @@ * members :) */ - // this flag is neutral, it may occur everywhere, we don't do anything with it - address.isPostal(); + String type = getParameterValue(address, net.fortuna.ical4j.vcard.Parameter.Id.TYPE); + if (type == null) { + return; + } + type = type.toLowerCase(); + // the 'postal' is neutral, it may occur everywhere, we don't do anything with it // These flags occur in contacts for organizations, hardly anyone includes all of these on // a personal business card, if these flags are tru, this means that this contact is a // contact to an organization, so we attach the address to the contact itself - if (address.isDomestic()) { + if (type.contains("dom")) { model.addStatement(addressResource, RDF.type, NCO.DomesticDeliveryAddress); addContactMediumProperty(model, contactResource, NCO.hasPostalAddress, addressResource, preferred); } - if (address.isInternational()) { + if (type.contains("intl")) { model.addStatement(addressResource, RDF.type, NCO.InternationalDeliveryAddress); addContactMediumProperty(model, contactResource, NCO.hasPostalAddress, addressResource, preferred); } - if (address.isParcel()) { + if (type.contains("parcel")) { model.addStatement(addressResource, RDF.type, NCO.ParcelDeliveryAddress); addContactMediumProperty(model, contactResource, NCO.hasPostalAddress, addressResource, preferred); } // this means that it is a work address of a person, we can attach it to the affiliation - if (address.isWork()) { + if (type.contains("work")) { if (affiliationResource != null) { // this means that an affiliation has already been created while processing // the organizational identity @@ -384,134 +458,138 @@ // this means that this is a home address, no additional types and attach it to the // contact - if (address.isHome()) { + if (type.contains("home")) { addContactMediumProperty(model, contactResource, NCO.hasPostalAddress, addressResource, preferred); } // and there is always the case that an address has no TYPE whatsoever - if (!address.isDomestic() && !address.isHome() && !address.isInternational() - && !address.isParcel() && !address.isPostal() && !address.isWork()) { + if (type == null || type.trim().length() == 0) { addContactMediumProperty(model, contactResource, NCO.hasPostalAddress, addressResource, preferred); } // nco doesn't support uid's and labels of addresses - // address.getUID(); - // address.getLabel() } } - private void processCommunications(Model model, Communications communications, Resource contactResource, + private void processCommunications(Model model, VCard communications, Resource contactResource, Resource affiliationResource) { - if (communications != null) { - EmailAddress preferredAddress = communications.getPreferredEmailAddress(); - for (EmailAddress address : communications.listEmailAddresses()) { - if (preferredAddress == address) { - processEmailAddress(model, address, contactResource, affiliationResource, true); - } - else { - processEmailAddress(model, address, contactResource, affiliationResource, false); - } + List<Property> mails = communications.getProperties(Id.EMAIL); + + //EmailAddress preferredAddress = communications.getPreferredEmailAddress(); + for (Property address : mails) { + String type = getParameterValue(address, net.fortuna.ical4j.vcard.Parameter.Id.TYPE); + + if (type != null && type.toLowerCase().contains("pref")) { + processEmailAddress(model, (Email)address, contactResource, affiliationResource, true); } - for (PhoneNumber number : communications.listPhoneNumbers()) { - processPhoneNumber(model, number, contactResource, affiliationResource); + else { + processEmailAddress(model, (Email)address, contactResource, affiliationResource, false); } } + + List<Property> phones = communications.getProperties(Id.TEL); + for (Property number : phones) { + processPhoneNumber(model, (Telephone)number, contactResource, affiliationResource); + } } - private void processPhoneNumber(Model model, PhoneNumber number, Resource contactResource, + private void processPhoneNumber(Model model, Telephone number, Resource contactResource, Resource affiliationResource) { - if (number != null) { - Resource numberResource = UriUtil.generateRandomResource(model); - model.addStatement(numberResource, RDF.type, NCO.PhoneNumber); - addStringProperty(model, numberResource, NCO.phoneNumber, number.getNumber()); - if (number.isBBS()) { - model.addStatement(numberResource, RDF.type, NCO.BbsNumber); + + Resource numberResource = UriUtil.generateRandomResource(model); + model.addStatement(numberResource, RDF.type, NCO.PhoneNumber); + addStringProperty(model, numberResource, NCO.phoneNumber, number.getValue()); + String type = getParameterValue(number, net.fortuna.ical4j.vcard.Parameter.Id.TYPE); + + if (type == null) { + return; + } + type = type.toLowerCase(); + if (type.contains("bbs")) { + model.addStatement(numberResource, RDF.type, NCO.BbsNumber); + } + if (type.contains("car")) { + model.addStatement(numberResource, RDF.type, NCO.CarPhoneNumber); + } + if (type.contains("cell")) { + model.addStatement(numberResource, RDF.type, NCO.CellPhoneNumber); + } + if (type.contains("fax")) { + model.addStatement(numberResource, RDF.type, NCO.FaxNumber); + } + if (type.contains("home")) { + addContactMediumProperty(model, contactResource, NCO.hasPhoneNumber, numberResource, + type.contains("pref")); + } + if (type.contains("isdn")) { + model.addStatement(numberResource, RDF.type, NCO.IsdnNumber); + } + if (type.contains("msg")) { + model.addStatement(numberResource, RDF.type, NCO.MessagingNumber); + } + if (type.contains("modem")) { + model.addStatement(numberResource, RDF.type, NCO.ModemNumber); + } + if (type.contains("pager")) { + model.addStatement(numberResource, RDF.type, NCO.PagerNumber); + } + if (type.contains("pcs")) { + model.addStatement(numberResource, RDF.type, NCO.PcsNumber); + } + if (type.contains("video")) { + model.addStatement(numberResource, RDF.type, NCO.VideoTelephoneNumber); + } + if (type.contains("voice")) { + model.addStatement(numberResource, RDF.type, NCO.VoicePhoneNumber); + } + if (type.contains("work")) { + if (affiliationResource != null) { + // this means that an affiliation has already been created while processing + // the organizational identity + addContactMediumProperty(model, affiliationResource, NCO.hasPhoneNumber, numberResource, + type.contains("pref")); } - if (number.isCar()) { - model.addStatement(numberResource, RDF.type, NCO.CarPhoneNumber); + else { + // this means that no affiliation has been created, we need to create one now + // an anonymous affiliation with an anonymous organization + affiliationResource = UriUtil.generateRandomResource(model); + model.addStatement(affiliationResource, RDF.type, NCO.Affiliation); + model.addStatement(contactResource, NCO.hasAffiliation, affiliationResource); + addContactMediumProperty(model, contactResource, NCO.hasPhoneNumber, numberResource, + type.contains("pref")); } - if (number.isCellular()) { - model.addStatement(numberResource, RDF.type, NCO.CellPhoneNumber); - } - if (number.isFax()) { - model.addStatement(numberResource, RDF.type, NCO.FaxNumber); - } - if (number.isHome()) { - addContactMediumProperty(model, contactResource, NCO.hasPhoneNumber, numberResource, number - .isPreferred()); - } - if (number.isISDN()) { - model.addStatement(numberResource, RDF.type, NCO.IsdnNumber); - } - if (number.isMessaging()) { - model.addStatement(numberResource, RDF.type, NCO.MessagingNumber); - } - if (number.isMODEM()) { - model.addStatement(numberResource, RDF.type, NCO.ModemNumber); - } - if (number.isPager()) { - model.addStatement(numberResource, RDF.type, NCO.PagerNumber); - } - if (number.isPCS()) { - model.addStatement(numberResource, RDF.type, NCO.PcsNumber); - } - if (number.isVideo()) { - model.addStatement(numberResource, RDF.type, NCO.VideoTelephoneNumber); - } - if (number.isVoice()) { - // isn't this nonsense?, if a voice telephone number is marked - // with this type, then what is a telephone number that is not - // marked with this type (sign-language videotelephone) - model.addStatement(numberResource, RDF.type, NCO.VoicePhoneNumber); - } - if (number.isWork()) { - if (affiliationResource != null) { - // this means that an affiliation has already been created while processing - // the organizational identity - addContactMediumProperty(model, affiliationResource, NCO.hasPhoneNumber, numberResource, - number.isPreferred()); - } - else { - // this means that no affiliation has been created, we need to create one now - // an anonymous affiliation with an anonymous organization - affiliationResource = UriUtil.generateRandomResource(model); - model.addStatement(affiliationResource, RDF.type, NCO.Affiliation); - model.addStatement(contactResource, NCO.hasAffiliation, affiliationResource); - addContactMediumProperty(model, contactResource, NCO.hasPhoneNumber, numberResource, - number.isPreferred()); - } - } + } - if (!number.isHome() && !number.isWork()) { - addContactMediumProperty(model, contactResource, NCO.hasPhoneNumber, numberResource, number - .isPreferred()); - } + if (!type.contains("home") && !type.contains("work")) { + addContactMediumProperty(model, contactResource, NCO.hasPhoneNumber, numberResource, + type.contains("pref")); } } - private void processEmailAddress(Model model, EmailAddress address, Resource contactResource, + private void processEmailAddress(Model model, Email address, Resource contactResource, Resource affiliationResource, boolean preferred) { /* * This sucks, there is no way to tell if an email is a private email or a business email this sucks * ass... */ - if (address != null && address.isType(EmailAddress.TYPE_INTERNET)) { + String type = getParameterValue(address, net.fortuna.ical4j.vcard.Parameter.Id.TYPE); + if (address != null && (type == null || type.toLowerCase().contains("internet"))) { // we don't support other than internet addresses anyway, I have // no idea what are these x.400 addresses // TODO solve the x.400 address type issue Resource addressResource = UriUtil.generateRandomResource(model); model.addStatement(addressResource, RDF.type, NCO.EmailAddress); - addStringProperty(model, addressResource, NCO.emailAddress, address.getAddress()); + addStringProperty(model, addressResource, NCO.emailAddress, address.getValue()); // due to the fact that the VCARD specs suck ass, we always attach the address // to the contact and never to the affiliation addContactMediumProperty(model, contactResource, NCO.hasEmailAddress, addressResource, preferred); } } - private void processGeographicalInformation(Model model, Resource contactResource, URI property, - GeographicalInformation geo) { + private void processGeographicalInformation(Model model, Resource contactResource, URI property, VCard vc) { + Geo geo = (Geo)vc.getProperty(Id.GEO); if (geo != null) { Resource geoResource = UriUtil.generateRandomResource(model); model.addStatement(geoResource, RDF.type, GEO.Point); @@ -521,34 +599,28 @@ } } - private void processImage(Model model, Resource contactResource, URI property, Image photo) { - if (photo != null) { - Resource imageResource = UriUtil.generateRandomResource(model); - model.addStatement(imageResource, RDF.type, NEXIF.Photo); - model.addStatement(imageResource, RDF.type, NFO.Attachment); - model.addStatement(contactResource, property, imageResource); - addStringProperty(model, imageResource, NIE.mimeType, photo.getContentType()); - } + private void processImage(Model model, Resource contactResource, URI property, String mimeType) { + Resource imageResource = UriUtil.generateRandomResource(model); + model.addStatement(imageResource, RDF.type, NEXIF.Photo); + model.addStatement(imageResource, RDF.type, NFO.Attachment); + model.addStatement(contactResource, property, imageResource); + addStringProperty(model, imageResource, NIE.mimeType, mimeType); } - private void processSound(Model model, Resource contactResource, URI property, Sound sound) { - if (sound != null) { - Resource soundResource = UriUtil.generateRandomResource(model); - model.addStatement(soundResource, RDF.type, NFO.Audio); - model.addStatement(soundResource, RDF.type, NFO.Attachment); - model.addStatement(contactResource, property, soundResource); - addStringProperty(model, soundResource, NIE.mimeType, sound.getContentType()); - } + private void processSound(Model model, Resource contactResource, URI property, String mimeType) { + Resource soundResource = UriUtil.generateRandomResource(model); + model.addStatement(soundResource, RDF.type, NFO.Audio); + model.addStatement(soundResource, RDF.type, NFO.Attachment); + model.addStatement(contactResource, property, soundResource); + addStringProperty(model, soundResource, NIE.mimeType, mimeType); } - private void processPublicKey(Model model, Resource contactResource, URI property, Key publicKey) { - if (publicKey != null) { - Resource keyResource = UriUtil.generateRandomResource(model); - model.addStatement(keyResource, RDF.type, NIE.InformationElement); - model.addStatement(keyResource, RDF.type, NFO.Attachment); - model.addStatement(contactResource, property, keyResource); - addStringProperty(model, keyResource, NIE.mimeType, publicKey.getContentType()); - } + private void processPublicKey(Model model, Resource contactResource, URI property, String mimeType) { + Resource keyResource = UriUtil.generateRandomResource(model); + model.addStatement(keyResource, RDF.type, NIE.InformationElement); + model.addStatement(keyResource, RDF.type, NFO.Attachment); + model.addStatement(contactResource, property, keyResource); + addStringProperty(model, keyResource, NIE.mimeType, mimeType); } private void addStringProperty(Model model, Resource resource, URI property, String value) { @@ -594,20 +666,79 @@ * @param marshaller the contact marshaller, may be used if the contact doesn't contain the UID property. * @return */ - private URI generateURIForContact(Contact contact, RDFContainer container, String contactHash) { + private URI generateURIForContact(VCard contact, RDFContainer container, String contactHash) { String contactIdentifier = null; - String uid = contact.getUID(); + Property uid = contact.getProperty(Id.UID); if (uid != null) { - contactIdentifier = uid; + contactIdentifier = uid.getValue(); } else { contactIdentifier = contactHash; } return createChildUri(container.getDescribedUri(), contactIdentifier); } - private String getContactHash(Contact contact, ContactMarshaller marshaller) { + private String getContactHash(VCard contact, VCardOutputter outputter) throws IOException, ValidationException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); - marshaller.marshallContact(stream, contact); + outputter.output(contact, stream); return StringUtil.sha1Hash(stream.toByteArray()); } + + private String getPropertyValue(VCard vc, Id fn) { + Property p = vc.getProperty(fn); + if (p != null) { + try { + return getDecodedPropertyalue(p); + } + catch (DecoderException e) { + // may happen, return the normal value instead + return p.getValue(); + } + } else { + return null; + } + } + + private String getParameterValue(Property photo, net.fortuna.ical4j.vcard.Parameter.Id type) { + Parameter param = photo.getParameter(type); + if (param != null) { + return param.getValue(); + } else { + return null; + } + } + + private boolean empty(Object [] array) { + if (array == null || array.length == 0) { + return true; + } else { + return false; + } + } + + private int length(Object[] arr) { + if (arr == null) { + return 0; + } else { + return arr.length; + } + } + + private String getDecodedPropertyalue(Property prop) throws DecoderException { + Encoding enc = (Encoding)prop.getParameter(Parameter.Id.ENCODING); + String val = prop.getValue(); + if (enc != null && enc.getValue().equalsIgnoreCase("QUOTED-PRINTABLE")) { + + /* + * A special Outlook2003 hack. + */ + if (val.endsWith("=")) { + val = val.substring(0,val.length() - 1); + } + + QuotedPrintableCodec codec = new QuotedPrintableCodec(); + return codec.decode(val); + } else { + return val; + } + } } Modified: trunk/aperture/src/test/org/semanticdesktop/aperture/docs/vcard-antoni-kontact.vcf =================================================================== --- trunk/aperture/src/test/org/semanticdesktop/aperture/docs/vcard-antoni-kontact.vcf 2009-02-25 13:26:01 UTC (rev 1749) +++ trunk/aperture/src/test/org/semanticdesktop/aperture/docs/vcard-antoni-kontact.vcf 2009-02-26 01:54:26 UTC (rev 1750) @@ -4,7 +4,7 @@ 63;Niemcy ADR;TYPE=intl;TYPE=postal;TYPE=work:;;Trippstadter Str. 122;Kaiserslautern; Rheinland-Pfalz;67663;Niemcy -BDAY:1985-01-28T00:00:00Z +BDAY:19850128T000000Z CLASS:PUBLIC EMAIL;TYPE=PREF:ant...@so... EMAIL:ant...@ot... @@ -362,7 +362,7 @@ XEnh3QjHZ+NPEehaJq0kf2mGxk1uGzcafJJIltM0MqWrgzNFM+WhUgEISShort9H+F3hLxk+r+I tdtZ7nVL/Vn+0zebGQwgsNPggVVmhmMaQ28cUCIjKgSNWKmVpJHK+CTw9k3VxKejajCDSel0m6q bSa0bSbXRdPa9rUvpCm1fRylUvb3bXs7X11tpvbRn/9k= -REV:2008-01-28T15:50:16Z +REV:20080128T155016Z ROLE:Software Developer TEL;TYPE=HOME:+48 91 3 177 6584 TEL;TYPE=WORK:+48 12 617 23 34 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |