lib/vpim/maker/vcard.rb in vpim-0.16 vs lib/vpim/maker/vcard.rb in vpim-0.17

- old
+ new

@@ -1,71 +1,91 @@ =begin - $Id: vcard.rb,v 1.7 2005/02/04 21:09:34 sam Exp $ + Copyright (C) 2006 Sam Roberts - Copyright (C) 2005 Sam Roberts - This library is free software; you can redistribute it and/or modify it under the same terms as the ruby language itself, see the file COPYING for details. =end require 'vpim/vcard' module Vpim - module Maker + module Maker #:nodoc: # A helper class to assist in building a vCard. # - # This idea is modelled after ruby 1.8's rss/maker classes. Perhaps all these methods - # should be added to Vpim::Vcard? + # Examples: + # - link:ex_mkvcard.txt: example of creating a vCard + # - link:ex_cpvcard.txt: example of copying and them modifying a vCard + # - link:ex_mkv21vcard.txt: example of creating version 2.1 vCard class Vcard - # Make a vCard for +full_name+. + # Make a vCard. # - # Yields +card+, a Vpim::Maker::Vcard to which fields can be added, and returns a Vpim::Vcard. + # Yields +maker+, a Vpim::Maker::Vcard which allows fields to be added to + # +card+, and returns +card+, a Vpim::Vcard. # - # Note that calling #add_name is required, all other fields are optional. - def Vcard.make(full_name, &block) # :yields: +card+ - new(full_name).make(&block) + # If +card+ is nil or not provided a new Vpim::Vcard is created and the + # fields are added to it. + # + # Defaults: + # - vCards must have both an N: and an FN: field, #make2 will fail if there + # is no FN: field in the +card+ when your block is finished adding fields. + # - If there is an FN: field, but no N: field, N: will be set from the information + # in FN:, see Vcard::Name#preformatted for more information. + # - vCards must have a VERSION: field. If one does not exist when your block is + # is finished adding fields then it will be set to 3.0. + def Vcard.make2(card = Vpim::Vcard.create, &block) # :yields: maker + new(nil, card).make(&block) end + # Deprecated, use #make2. + # + # If set, the FN: field will be set to +full_name+. Otherwise, FN: will + # be set from the values in #add_name. + def Vcard.make(full_name = nil, &block) # :yields: maker + new(full_name, Vpim::Vcard.create).make(&block) + end + def make # :nodoc: yield self - if !@initialized_N - raise Vpim::InvalidEncodingError, 'It is mandatory to have a N field, see #add_name.' + unless @card['N'] + raise Vpim::InvalidEncodingError, 'It is mandatory to have a name field, see #add_name.' end + unless @card['FN'] + @card << Vpim::DirectoryInfo::Field.create('FN', Vpim::Vcard::Name.new(@card['N'], '').formatted) + end + unless @card['VERSION'] + @card << Vpim::DirectoryInfo::Field.create('VERSION', "3.0") + end @card end private - def initialize(full_name) # :nodoc: - @card = Vpim::Vcard::create - @card << Vpim::DirectoryInfo::Field.create('FN', full_name ) - @initialized_N = false - # pp @card + def initialize(full_name, card) # :nodoc: + @card = card || Vpim::Vcard::create + if full_name + @card << Vpim::DirectoryInfo::Field.create('FN', full_name ) + end end public - # Add an arbitrary Field, +field+. - def add_field(field) - @card << field - end - - # Add a name field, N. + # Add a name field, N:. # - # Warning: This is the only mandatory field, besides the full name, which - # is added from Vcard.make's +full_name+. - # # Attributes of N are: # - family: family name # - given: given name # - additional: additional names # - prefix: such as "Ms." or "Dr." # - suffix: such as "BFA", or "Sensei" # # All attributes are optional. # + # Warning: This is the only mandatory field besides the full name, FN, + # but FN: can be set in #make, and if not set will be constucted as the + # string "#{prefix} #{given} #{additional} #{family}, #{suffix}". + # # FIXME: is it possible to deduce given/family from the full_name? # # FIXME: Each attribute can currently only have a single String value. # # FIXME: Need to escape specials in the String. @@ -74,17 +94,27 @@ yield x @card << Vpim::DirectoryInfo::Field.create( 'N', x.map { |s| s ? s.to_str : '' } ) - @initialized_N = true self end - # Add a address field, ADR. + # Add a full name field, FN. # - # Attributes of ADR that describe the address are: + # Normally the FN field value is derived from the N: field value, but + # it can be explicitly set. + def fullname=(fullname) + if @card.field('FN') + raise Vpim::InvalidEncodingError, "Not allowed to add more than one FN field to a vCard." + end + @card << Vpim::DirectoryInfo::Field.create( 'FN', fullname ); + end + + # Add a address field, ADR:. + # + # Attributes of ADR: that describe the address are: # - pobox: post office box # - extended: seldom used, its not clear what it is for # - street: street address, multiple components should be separated by a comma, ',' # - locality: usually the city # - region: usually the province or state @@ -97,13 +127,13 @@ # - delivery: postal, parcel, dom (domestic), intl (international) - rarely used # # All attributes are optional. #location and #home can be set to arrays of # strings. # - # TODO: Add #label to support LABEL. + # TODO - Add #label to support LABEL. # - # FIXME: Need to escape specials in the String. + # FIXME - Need to escape specials in the String. def add_addr # :yield: adr x = Struct.new( :location, :preferred, :delivery, :pobox, :extended, :street, :locality, :region, :postalcode, :country ).new @@ -122,16 +152,16 @@ @card << Vpim::DirectoryInfo::Field.create( 'ADR', values, paramshash) self end - # Add a telephone number field, TEL. + # Add a telephone number field, TEL:. # # +number+ is supposed to be a "X.500 Telephone Number" according to RFC 2426, if you happen # to be familiar with that. Otherwise, anything that looks like a phone number should be OK. # - # Attributes of TEL are: + # Attributes of TEL: are: # - location: home, work, msg, cell, car, pager - often used, can be set to other values # - preferred: true - often used, set if this is the preferred telephone number # - capability: voice,fax,video,bbs,modem,isdn,pcs - fax is useful, the others are rarely used # # All attributes are optional, and so is the block. @@ -151,13 +181,13 @@ @card << Vpim::DirectoryInfo::Field.create( 'TEL', number, params) self end - # Add a email address field, EMAIL. + # Add a email address field, EMAIL:. # - # Attributes of EMAIL are: + # Attributes of EMAIL: are: # - location: home, work - often used, can be set to other values # - preferred: true - often used, set if this is the preferred email address # - protocol: internet,x400 - internet is the default, set this for other kinds # # All attributes are optional, and so is the block. @@ -177,16 +207,16 @@ @card << Vpim::DirectoryInfo::Field.create( 'EMAIL', email, params) self end - # Add a nickname field, NICKNAME. + # Add a nickname field, NICKNAME:. def nickname=(nickname) @card << Vpim::DirectoryInfo::Field.create( 'NICKNAME', nickname ); end - # Add a birthday field, BDAY. + # Add a birthday field, BDAY:. # # +birthday+ must be a time or date object. # # Warning: It may confuse both humans and software if you add multiple # birthdays. @@ -203,14 +233,14 @@ def note=(note) @card << Vpim::DirectoryInfo::Field.create( 'NOTE', note ); end =end - # Add an instant-messaging/point of presence address field, IMPP. The address + # Add an instant-messaging/point of presence address field, IMPP:. The address # is a URL, with the syntax depending on the protocol. # - # Attributes of IMPP are: + # Attributes of IMPP: are: # - preferred: true - set if this is the preferred address # - location: home, work, mobile - location of address # - purpose: personal,business - purpose of communications # # All attributes are optional, and so is the block. @@ -218,11 +248,11 @@ # The URL syntaxes for the messaging schemes is fairly complicated, so I # don't try and build the URLs here, maybe in the future. This forces # the user to know the URL for their own address, hopefully not too much # of a burden. # - # IMPP is defined in draft-jennings-impp-vcard-04.txt. It refers to the + # IMPP: is defined in draft-jennings-impp-vcard-04.txt. It refers to the # URI scheme of a number of messaging protocols, but doesn't give # references to all of them: # - "xmpp" indicates to use XMPP, draft-saintandre-xmpp-uri-06.txt # - "irc" or "ircs" indicates to use IRC, draft-butcher-irc-url-04.txt # - "sip" indicates to use SIP/SIMPLE, RFC 3261 @@ -248,17 +278,17 @@ @card << Vpim::DirectoryInfo::Field.create( 'IMPP', url, params) self end - # Add an Apple style AIM account name, +xaim+ is an AIM screen name. + # Add an X-AIM: account name where +xaim+ is an AIM screen name. # # I don't know if this is conventional, or supported by anything other # than AddressBook.app, but an example is: # X-AIM;type=HOME;type=pref:exampleaccount # - # Attributes of X-AIM are: + # Attributes of X-AIM: are: # - preferred: true - set if this is the preferred address # - location: home, work, mobile - location of address # # All attributes are optional, and so is the block. def add_x_aim(xaim) # :yield: xaim @@ -279,14 +309,14 @@ @card << Vpim::DirectoryInfo::Field.create( 'X-AIM', xaim, params) self end - # Add a photo field, PHOTO. + # Add a photo field, PHOTO:. # - # Attributes of PHOTO are: - # - image: set to image data to inclue inline + # Attributes of PHOTO: are: + # - image: set to image data to include inline # - link: set to the URL of the image data # - type: string identifying the image type, supposed to be an "IANA registered image format", # or a non-registered image format (usually these start with an x-) # # An error will be raised if neither image or link is set, or if both image @@ -327,9 +357,57 @@ end end @card << Vpim::DirectoryInfo::Field.create( 'PHOTO', value, params ) self + end + + # Add a Field, +field+. + def add_field(field) + fieldname = field.name.upcase + case + when [ 'BEGIN', 'END' ].include?(fieldname) + raise Vpim::InvalidEncodingError, "Not allowed to manually add #{field.name} to a vCard." + + when [ 'VERSION', 'N', 'FN' ].include?(fieldname) + if @card.field(fieldname) + raise Vpim::InvalidEncodingError, "Not allowed to add more than one #{fieldname} to a vCard." + end + @card << field + + else + @card << field + end + end + + # Copy the fields from +card+ into self using #add_field. If a block is + # provided, each Field from +card+ is yielded. The block should return a + # Field to add, or nil. The Field doesn't have to be the one yielded, + # allowing the field to be copied and modified (see Field#copy) before adding, or + # not added at all if the block yields nil. + # + # The vCard fields BEGIN: and END: aren't copied, and VERSION:, N:, and FN: are copied + # only if the card doesn't have them already. + def copy(card) # :yields: Field + card.each do |field| + fieldname = field.name.upcase + case + when [ 'BEGIN', 'END' ].include?(fieldname) + # Never copy these + + when [ 'VERSION', 'N', 'FN' ].include?(fieldname) && @card.field(fieldname) + # Copy these only if they don't already exist. + + else + if block_given? + field = yield field + end + + if field + add_field(field) + end + end + end end end end end