lib/vpim/maker/vcard.rb in vpim-0.323 vs lib/vpim/maker/vcard.rb in vpim-0.357

- old
+ new

@@ -7,412 +7,10 @@ =end require 'vpim/vcard' module Vpim - module Maker #:nodoc: - # A helper class to assist in building a 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 - # - link:ex_mkyourown.txt: example of adding support for new fields to Maker::Vcard - class Vcard - # Make a vCard. - # - # Yields +maker+, a Vpim::Maker::Vcard which allows fields to be added to - # +card+, and returns +card+, a Vpim::Vcard. - # - # 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 - 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, card) # :nodoc: - @card = card || Vpim::Vcard::create - if full_name - @card << Vpim::DirectoryInfo::Field.create('FN', full_name ) - end - end - - public - - # Add a name field, N:. - # - # 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. - # FN: can be set in #make, or by #fullname=, and if not set will be - # constucted as the string "#{prefix} #{given} #{additional} #{family}, - # #{suffix}". - def add_name # :yield: n - # FIXME: Each attribute can currently only have a single String value. - # FIXME: Need to escape specials in the String. - x = Struct.new(:family, :given, :additional, :prefix, :suffix).new - yield x - @card << Vpim::DirectoryInfo::Field.create( - 'N', - x.map { |s| s ? s.to_str : '' } - ) - self - end - - # Add a full name field, FN. - # - # Normally the FN field value is derived from the N: field value, see - # #add_name, 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 - # - postalcode: postal code - # - country: country name, no standard for country naming is specified - # - # Attributes that describe how the address is used, and customary values, are: - # - location: home, work - often used, can be set to other values - # - preferred: true - often used, set if this is the preferred address - # - 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. - def add_addr # :yield: adr - # FIXME - Need to escape specials in the String. - x = Struct.new( - :location, :preferred, :delivery, - :pobox, :extended, :street, :locality, :region, :postalcode, :country - ).new - yield x - - values = x.to_a[3, 7].map { |s| s ? s.to_str : '' } - - # All these attributes go into the TYPE parameter. - params = [ x[:location], x[:delivery] ] - params << 'PREF' if x[:preferred] - params = params.flatten.uniq.compact.map { |s| s.to_str } - - paramshash = {} - - paramshash['TYPE'] = params if params.first - - @card << Vpim::DirectoryInfo::Field.create( 'ADR', values, paramshash) - self - end - - # 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: - # - 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. - def add_tel(number) # :yield: tel - params = {} - if block_given? - x = Struct.new( :location, :preferred, :capability ).new - - yield x - - x[:preferred] = 'PREF' if x[:preferred] - - types = x.to_a.flatten.uniq.compact.map { |s| s.to_str } - - params['TYPE'] = types if types.first - end - - @card << Vpim::DirectoryInfo::Field.create( 'TEL', number, params) - self - end - - # Add a email address field, EMAIL:. - # - # 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. - def add_email(email) # :yield: email - params = {} - if block_given? - x = Struct.new( :location, :preferred, :protocol ).new - - yield x - - x[:preferred] = 'PREF' if x[:preferred] - - types = x.to_a.flatten.uniq.compact.map { |s| s.to_str } - - params['TYPE'] = types if types.first - end - - @card << Vpim::DirectoryInfo::Field.create( 'EMAIL', email, params) - self - end - - # Add a nickname field, NICKNAME:. - def nickname=(nickname) - @card << Vpim::DirectoryInfo::Field.create( 'NICKNAME', nickname ); - end - - # 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. - def birthday=(birthday) - if !birthday.respond_to? :month - raise Vpim::InvalidEncodingError, 'birthday doesn\'t have #month, so it is not a date or time object.' - end - @card << Vpim::DirectoryInfo::Field.create( 'BDAY', birthday ); - end - -=begin -TODO - need text=() implemented in Field - - # Add a note field, NOTE. It can contain newlines, they will be escaped. - def note=(note) - @card << Vpim::DirectoryInfo::Field.create( 'NOTE', note ); - end -=end - - # 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: - # - 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. - # - # 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 - # 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 - # - "im" or "pres" indicates to use a CPIM or CPP gateway, RFC 3860 and RFC 3859 - # - "ymsgr" indicates to use yahoo - # - "msn" might indicate to use Microsoft messenger - # - "aim" indicates to use AOL - # - def add_impp(url) # :yield: impp - params = {} - - if block_given? - x = Struct.new( :location, :preferred, :purpose ).new - - yield x - - x[:preferred] = 'PREF' if x[:preferred] - - types = x.to_a.flatten.uniq.compact.map { |s| s.upcase } - - params['TYPE'] = types if types.first - end - - @card << Vpim::DirectoryInfo::Field.create( 'IMPP', url, params) - self - end - - # 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: - # - 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 - params = {} - - if block_given? - x = Struct.new( :location, :preferred ).new - - yield x - - x[:preferred] = 'PREF' if x[:preferred] - - types = x.to_a.flatten.uniq.compact.map { |s| s.upcase } - - params['TYPE'] = types if types.first - end - - @card << Vpim::DirectoryInfo::Field.create( 'X-AIM', xaim, params) - self - end - - - # Add a photo field, PHOTO:. - # - # 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 - # and link is set. - # - # Setting type is optional for a link image, because either the URL, the - # image file extension, or a HTTP Content-Type may specify the type. If - # it's not a link, setting type is mandatory, though it can be set to an - # empty string, <code>''</code>, if the type is unknown. - # - # TODO - I'm not sure about this API. I'm thinking maybe it should be - # #add_photo(image, type), and that I should detect when the image is a - # URL, and make type mandatory if it wasn't a URL. - def add_photo # :yield: photo - x = Struct.new(:image, :link, :type).new - yield x - if x[:image] && x[:link] - raise Vpim::InvalidEncodingError, 'Image is not allowed to be both inline and a link.' - end - - value = x[:image] || x[:link] - - if !value - raise Vpim::InvalidEncodingError, 'A image link or inline data must be provided.' - end - - params = {} - - # Don't set type to the empty string. - params['TYPE'] = x[:type] if( x[:type] && x[:type].length > 0 ) - - if x[:link] - params['VALUE'] = 'URI' - else # it's inline, base-64 encode it - params['ENCODING'] = :b64 - if !x[:type] - raise Vpim::InvalidEncodingError, 'Inline image data must have it\'s type set.' - end - end - - @card << Vpim::DirectoryInfo::Field.create( 'PHOTO', value, params ) - self - end - - # Add a URL field, URL:. - def add_url(url) - @card << Vpim::DirectoryInfo::Field.create( 'URL', url.to_str ); - 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 + module Maker #:nodoc:backwards compat + Vcard = Vpim::Vcard::Maker #:nodoc:backwards compat end end