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