xmlresume2x 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,57 @@
1
+ xmlresume2x: converts an xml resume to various output formats
2
+
3
+ Copyright (C) 2004 Thomas Leitner
4
+
5
+ = Description
6
+
7
+ The "XML Resume Library" at http://xmlresume.sourceforge.net provides a way for storing one's resume
8
+ data in a consistent way. The XML r�sum� can be transformed to various output formats, including
9
+ text, HTML and PDF by utilizing XSLT.
10
+
11
+ I came across the European Curriculum Vitae format at http://www.cedefop.eu.int/transparency/cv.asp
12
+ which provides a standard for CVs in Europe. Nicola Vitacolonna has used this information to provide
13
+ a LaTeX class - europecv (http://www.ctan.org/tex-archive/help/Catalogue/entries/europecv.html) -
14
+ which (nearly) conforms to this standard. As the XML R�sum� Library does not have a converter for
15
+ this, I wrote this program to convert an XML r�sum� to a LaTeX file which uses the europecv class.
16
+
17
+ During the development process I realized that it would be useful to add support for other output
18
+ formats. Therefore I changed the conversion process to use configuration files so that this program
19
+ can be used to transform a r�sum� to any output format.
20
+
21
+ = Dependencies
22
+
23
+ No Ruby dependencies but if you want to convert the resulting LaTeX file to a PDF file, you need to
24
+ have LaTeX and the europecv class installed.
25
+
26
+ = Installation
27
+
28
+ $ ruby setup.rb config
29
+ $ ruby setup.rb setup
30
+ $ ruby setup.rb install
31
+
32
+ Or you could use Rake and substitute the above commands with this:
33
+ $ rake install
34
+
35
+ Or you could use the RPA way:
36
+ $ rpa install xmlresume2x
37
+
38
+ Or you could use the RubyGem way:
39
+ $ gem install xmlresume2x
40
+
41
+ = Documentation
42
+
43
+ The documentation is located in the "doc/output" directory and has to be generated with webgen
44
+ (http://webgen.rubyforge.org) by issuing the command <code>rake doc</code>. If you do not want to do
45
+ this, you can still view the whole documentation for the latest version at
46
+ http://xmlresume2x.rubyforge.org.
47
+
48
+ = Example
49
+
50
+ Example files are provided in the "testfiles" directory.
51
+
52
+ = Contact
53
+
54
+ Author: Thomas Leitner
55
+ * Web: xmlresume2x.rubyforge.org
56
+ * e-Mail: t_leitner@gmx.at
57
+ * GPG Key-Id: 0xD942E7F6
@@ -0,0 +1,275 @@
1
+ # -*- ruby -*-
2
+ #
3
+ # $Id$
4
+ #
5
+ # xmlresume2x: converts an xml resume to various output formats
6
+ # Copyright (C) 2004 Thomas Leitner
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify it under the terms of the GNU
9
+ # General Public License as published by the Free Software Foundation; either version 2 of the
10
+ # License, or (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
13
+ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License along with this program; if not,
17
+ # write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ #
19
+
20
+
21
+ begin
22
+ require 'rubygems'
23
+ require 'rake/gempackagetask'
24
+ rescue Exception
25
+ nil
26
+ end
27
+
28
+ require 'rake/clean'
29
+ require 'rake/packagetask'
30
+ require 'rake/rdoctask'
31
+ require 'rake/testtask'
32
+
33
+ # General actions ##############################################################
34
+
35
+ $:.push "lib"
36
+ require 'xmlresume2x/converter'
37
+
38
+ PKG_NAME = "xmlresume2x"
39
+ PKG_VERSION = XMLResume2x::VERSION.join( '.' )
40
+ PKG_FULLNAME = PKG_NAME + "-" + PKG_VERSION
41
+ PKG_SUMMARY = "Converts an xml resume to various output formats"
42
+ PKG_DESCRIPTION = "xmlresume2x can convert CVs written in the XML Resume
43
+ Library format (http://xmlresume.sourceforge.net) to a number
44
+ of formats, including LaTeX markup which uses the europecv
45
+ (http://www.ctan.org/tex-archive/help/Catalogue/entries/europecv.html)
46
+ class which is based on the 'standard' European Curriculum Vitae
47
+ format at http://www.cedefop.eu.int/transparency/cv.asp."
48
+
49
+ SRC_RB = FileList['lib/**/*.rb', 'bin/**/*', 'data/**/*.cfg']
50
+
51
+ # The default task is run if rake is given no explicit arguments.
52
+
53
+ desc "Default Task"
54
+ task :default => :version
55
+
56
+
57
+ # End user tasks ################################################################
58
+
59
+ desc "Prepares for installation"
60
+ task :prepare do
61
+ ruby "setup.rb config"
62
+ ruby "setup.rb setup"
63
+ end
64
+
65
+
66
+ desc "Installs the package #{PKG_NAME}"
67
+ task :install => [:prepare] do
68
+ ruby "setup.rb install"
69
+ end
70
+
71
+
72
+ task :clean do
73
+ ruby "setup.rb clean"
74
+ end
75
+
76
+
77
+ CLOBBER << "doc/output" << FileList['texput.*'].to_a
78
+ desc "Builds the documentation (needs webgen and LaTeX with latex-europecv package installed)"
79
+ task :doc => [:rdoc, :gen_version] do
80
+ chdir "doc" do
81
+ sh "webgen -V 3"
82
+ end
83
+ ruby "-Ilib bin/xmlresume2x -d data/xmlresume2x -f xhtml -l en testfiles/example1.xml > doc/output/examples/example1.html"
84
+ ruby "-Ilib bin/xmlresume2x -d data/xmlresume2x -f xhtml -l en testfiles/example2.xml > doc/output/examples/example2.html"
85
+ ruby "-Ilib bin/xmlresume2x -d data/xmlresume2x -f latex-europecv -l en testfiles/example1.xml | latex"
86
+ sh "dvipdf texput.dvi doc/output/examples/example1.pdf"
87
+ ruby "-Ilib bin/xmlresume2x -d data/xmlresume2x -f latex-europecv -l en testfiles/example2.xml | latex"
88
+ sh "dvipdf texput.dvi doc/output/examples/example2.pdf"
89
+ sh "cp testfiles/example*.xml doc/output/examples"
90
+ end
91
+
92
+ rd = Rake::RDocTask.new do |rdoc|
93
+ rdoc.rdoc_dir = 'doc/output/rdoc'
94
+ rdoc.title = PKG_NAME
95
+ rdoc.options << '--line-numbers' << '--inline-source' << '-m README'
96
+ rdoc.rdoc_files.include( 'README' )
97
+ rdoc.rdoc_files.include( 'lib/**/*.rb' )
98
+ end
99
+
100
+ # Developer tasks ##############################################################
101
+
102
+ PKG_FILES = FileList.new( [
103
+ 'setup.rb',
104
+ 'TODO',
105
+ 'COPYING',
106
+ 'README',
107
+ 'Rakefile',
108
+ 'ChangeLog',
109
+ 'VERSION',
110
+ 'install.rb',
111
+ 'bin/**/*',
112
+ 'lib/**/*.rb',
113
+ 'doc/**/*',
114
+ 'test/**/*',
115
+ 'data/**/*'
116
+ ]) do |fl|
117
+ fl.exclude( /\bsvn\b/ )
118
+ fl.exclude( 'doc/output' )
119
+ end
120
+
121
+ task :package => [:gen_files] do
122
+ chdir 'pkg' do
123
+ sh "rpaadmin packport #{PKG_NAME}-#{PKG_VERSION}"
124
+ end
125
+ end
126
+
127
+ task :gen_changelog do
128
+ sh "svn log -r HEAD:1 -v > ChangeLog"
129
+ end
130
+
131
+ task :gen_version do
132
+ puts "Generating VERSION file"
133
+ File.open( 'VERSION', 'w+' ) do |file| file.write( PKG_VERSION + "\n" ) end
134
+ end
135
+
136
+ task :gen_installrb do
137
+ puts "Generating install.rb file"
138
+ File.open( 'install.rb', 'w+' ) do |file|
139
+ file.write "
140
+ require 'rpa/install'
141
+
142
+ class Install_#{PKG_NAME} < RPA::Install::FullInstaller
143
+ name '#{PKG_NAME}'
144
+ version '#{PKG_VERSION}-1'
145
+ classification Application
146
+ build do
147
+ installdocs %w[COPYING ChangeLog TODO]
148
+ installdocs 'docs'
149
+ installrdoc %w[README] + Dir['lib/**/*.rb']
150
+ installdata
151
+ end
152
+ description <<-EOF
153
+ #{PKG_SUMMARY}
154
+
155
+ #{PKG_DESCRIPTION}
156
+ EOF
157
+ end
158
+ "
159
+ end
160
+ end
161
+
162
+ task :gen_files => [:gen_changelog, :gen_version, :gen_installrb]
163
+ CLOBBER << "ChangeLog" << "VERSION" << "install.rb"
164
+
165
+ Rake::PackageTask.new( PKG_NAME, PKG_VERSION ) do |p|
166
+ p.need_tar = true
167
+ p.need_zip = true
168
+ p.package_files = PKG_FILES
169
+ end
170
+
171
+ if defined? Gem
172
+ spec = Gem::Specification.new do |s|
173
+
174
+ #### Basic information
175
+
176
+ s.name = PKG_NAME
177
+ s.version = PKG_VERSION
178
+ s.summary = PKG_SUMMARY
179
+ s.description = PKG_DESCRIPTION
180
+
181
+ #### Dependencies, requirements and files
182
+
183
+ s.files = PKG_FILES.to_a
184
+
185
+ s.require_path = 'lib'
186
+ s.autorequire = nil
187
+ s.executables = ['xmlresume2x']
188
+ s.default_executable = 'xmlresume2x'
189
+
190
+ #### Documentation
191
+
192
+ s.has_rdoc = true
193
+ s.extra_rdoc_files = rd.rdoc_files.reject do |fn| fn =~ /\.rb$/ end.to_a
194
+ s.rdoc_options = ['--line-numbers', '-m README']
195
+
196
+ #### Author and project details
197
+
198
+ s.author = "Thomas Leitner"
199
+ s.email = "t_leitner@gmx.at"
200
+ s.homepage = "xmlresume2x.rubyforge.org"
201
+ s.rubyforge_project = "xmlresume2x"
202
+ end
203
+
204
+ Rake::GemPackageTask.new( spec ) do |pkg|
205
+ pkg.need_zip = true
206
+ pkg.need_tar = true
207
+ end
208
+ end
209
+
210
+
211
+ desc "Creates a tag in the repository"
212
+ task :tag do
213
+ repositoryPath = File.dirname( $1 ) if `svn info` =~ /^URL: (.*)$/
214
+ fail "Tag already created in repository " if /#{PKG_FULLNAME}/ =~ `svn ls #{repositoryPath}/versions`
215
+ sh "svn cp -m 'Created version #{PKG_FULLNAME}' #{repositoryPath}/trunk #{repositoryPath}/versions/#{PKG_FULLNAME}"
216
+ end
217
+
218
+ desc "Upload documentation to homepage"
219
+ task :uploaddoc => [:doc] do
220
+ Dir.chdir('doc/output')
221
+ sh "scp -r * gettalong@rubyforge.org:/var/www/gforge-projects/#{PKG_NAME}/"
222
+ end
223
+
224
+
225
+ # Misc tasks ###################################################################
226
+
227
+
228
+ def count_lines( filename )
229
+ lines = 0
230
+ codelines = 0
231
+ open( filename ) do |f|
232
+ f.each do |line|
233
+ lines += 1
234
+ next if line =~ /^\s*$/
235
+ next if line =~ /^\s*#/
236
+ codelines += 1
237
+ end
238
+ end
239
+ [lines, codelines]
240
+ end
241
+
242
+
243
+ def show_line( msg, lines, loc )
244
+ printf "%6s %6s %s\n", lines.to_s, loc.to_s, msg
245
+ end
246
+
247
+ desc "Show statistics"
248
+ task :statistics do
249
+ total_lines = 0
250
+ total_code = 0
251
+ show_line( "File Name", "Lines", "LOC" )
252
+ SRC_RB.each do |fn|
253
+ lines, codelines = count_lines fn
254
+ show_line( fn, lines, codelines )
255
+ total_lines += lines
256
+ total_code += codelines
257
+ end
258
+ show_line( "Total", total_lines, total_code )
259
+ end
260
+
261
+
262
+ desc "Show unprocessed elements"
263
+ task :missing do
264
+ require 'set'
265
+ elements = File.read( 'misc/dtd/resume.dtd' ).scan( /^(<!ELEMENT\s)(\w+)\s(?!\(#PCDATA\))/ ).collect {|i| i[1]}.to_set
266
+ Dir['data/xmlresume2x/format/*.cfg'].each do |file|
267
+ processor = XMLResume2x::ResumeProcessor.new( 'en' )
268
+ processor.load_config( File.read( file ) )
269
+ processed = processor.assignments.keys
270
+ e = elements.dup
271
+ e.subtract( processed )
272
+ puts "Missing elements for format file #{file}:"
273
+ puts e.to_a.sort.join( ", " )
274
+ end
275
+ end
data/TODO ADDED
@@ -0,0 +1,37 @@
1
+ * add some css files to distribution
2
+ * add TOC-menu to xhtml format
3
+ * generalize formating of publications, look at pub.xsl
4
+ * add support for multiple pubs, memberships, ...???
5
+ * most words in lang files should be lowercase, config files should capitalize words correctly
6
+ * add Spanish and Esperanto language support
7
+ * add output filter for text (use Text::Format)
8
+
9
+ ---- FINISHED ----
10
+
11
+ * add OptionParser for reading in template and xml file
12
+ * add 'calculated' stub
13
+ * add basic ERB functionality so that the template can be translated
14
+ * combine Element2Latex and Text2Latex as they process text parts (%inlines; or #PCDATA)
15
+ * combine Text2Latex and CalculatedEntries and use uniform interface
16
+ * refactor Text2Latex (ev. use subclasses, ie. base class Element, then AddressElement, LocationElement, ...)
17
+ * think about moving the formatting of the calculated parts to a YAML file
18
+ * add calculated items for period, periodOrDate
19
+ * add more element processors to configuration file (move most of the processing to the configuration file)
20
+ * add more blocks to the template file
21
+ * BUG: whitespace should be squeezed sometimes and sometimes not (ie. always in para, not in address...)
22
+ * externalize strings for i18n
23
+ * change name of program to xmlresume2x
24
+ * make a list of all processed and not processed elements (put it into Rakefile)
25
+ * add processors for the rest of the xml resume dtd
26
+ * add "free form" output for addresses (i.e. convert \n to \newline{} but only here, not everywhere!)
27
+ * BUG: list for membership, referees, awards,...
28
+ * BUG: two history sections result in two "Employment History" captions, same with academics, pubs,...
29
+ * BUG: latex output has to be in latin1 format, how to convert?
30
+ * finish LaTeX output
31
+ * incorporate changed install.rb for RPA from Mauricio
32
+ * add French
33
+ * make RubyGems version by using information/code supplied at ruby-talk by Mauricio
34
+ * provide Array#separate_by( normal, last ) for use with major/minor list
35
+ * provide sample output on homepage for test r�sum�s
36
+ * BUG: iconv not available on windows -> use other tool (eventually #pack and #unpack)
37
+ * add output filter for HTML
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # $Id: xmlresume2europecv.rb 6 2004-10-14 10:36:22Z thomas $
4
+ #
5
+ # xmlresume2europecv: converts an xml resume to a europecv LaTeX file
6
+ # Copyright (C) 2004 Thomas Leitner
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify it under the terms of the GNU
9
+ # General Public License as published by the Free Software Foundation; either version 2 of the
10
+ # License, or (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
13
+ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License along with this program; if not,
17
+ # write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ #
19
+
20
+ require 'optparse'
21
+ require 'ostruct'
22
+ require 'xmlresume2x/converter'
23
+
24
+ converter = XMLResume2x::Converter.new
25
+
26
+ # Try to parse data dir independently to show correct help messages
27
+ begin
28
+ OptionParser.new do |opt|
29
+ opt.on( "-d", "--data-dir DATADIR" ) {|converter.data_dir|}
30
+ opt.on( "-h", "--help" ) {}
31
+ opt.on( "-v", "--version" ) {}
32
+ end.permute( ARGV )
33
+ rescue OptionParser::ParseError
34
+ end
35
+
36
+ options = OpenStruct.new
37
+
38
+ opt = OptionParser.new do |opt|
39
+ opt.banner = "Usage: xmlresume2x [options] RESUME.XML "
40
+ opt.separator "Options:"
41
+ opt.separator ""
42
+ opt.on( "-f", "--format FORMAT", converter.available_formats, "Use FORMAT as the output format (#{converter.available_formats.join(', ')})" ) {|options.format|}
43
+ opt.on( "-l", "--lang LANG", converter.available_langs, "Use LANG as the resume language (#{converter.available_langs.join(', ')})" ) {|options.language|}
44
+ opt.on( "-c", "--config CONFIG", "Use CONFIG as additional configuration file (can be used to override default behaviour)" ) {|options.userconfig|}
45
+ opt.on( "-d", "--data-dir DATADIR", "Use the directory DATADIR as data directory" ) {|converter.data_dir|}
46
+ opt.on( "-h", "--help" ) { puts opt; exit 1}
47
+ end
48
+
49
+ files = opt.permute( ARGV )
50
+ raise OptionParser::NeedlessArgument.new( *files[1..-1] ) if files.length > 1
51
+ if options.format.nil? || options.language.nil?
52
+ $stderr.puts "Error: You need to specify the FORMAT and the LANGUAGE!!!"
53
+ $stderr.puts opt;
54
+ exit 1
55
+ end
56
+ converter.load_config( options.format, options.language, options.userconfig )
57
+ puts converter.convert( files[0] )
@@ -0,0 +1,154 @@
1
+ # Common configuration for the resume converter -*- ruby -*-
2
+ # - Includes assignments for simple processors
3
+ # - Includes common processors for
4
+ # - name, location, present, date, from, to, period, birth
5
+ # - address
6
+ # - contact
7
+
8
+ #####################################################
9
+ # Assignments for simple processors
10
+
11
+ add_processor 'DefaultProcessor' do |element|
12
+ element.process_children!
13
+ end
14
+
15
+ processor ['description', 'note', 'misc', 'legalnotice', 'objective'] => 'SubParaProcessor'
16
+
17
+ processor ['achievement', 'artTitle', 'bookTitle', 'employer', 'institution',
18
+ 'organization', 'para', 'publisher', 'skill'] => 'DefaultProcessor'
19
+
20
+
21
+ #####################################################
22
+ # Name, location, ... processors
23
+
24
+ processor 'name' => 'NameProcessor' do |n|
25
+ [n.title, n.firstname, (n.middlenames.join(' ') if n.middlenames), n.surname, n.suffix].compact.join( ' ' )
26
+ end
27
+
28
+ processor 'location' => 'LocationProcessor' do |l|
29
+ [l.city, l.state, l.province, l.county, l.country].compact.join( ', ' )
30
+ end
31
+
32
+ processor 'present' => 'PresentProcessor' do |present|
33
+ keyword( :Present )
34
+ end
35
+
36
+ processor 'date' => 'DateProcessor' do |date|
37
+ day = (date.dayOfMonth.nil? ? '' : "#{date.dayOfMonth}. ")
38
+ month = (date.month.nil? ? '' : "#{date.month} ")
39
+ "#{day}#{month}#{date.year}"
40
+ end
41
+
42
+ processor ['from', 'to'] => 'FromToProcessor' do |element|
43
+ [element.date, element.present].compact.join( '' )
44
+ end
45
+
46
+ processor 'birth' => 'BirthProcessor' do |birth|
47
+ birth.date
48
+ end
49
+
50
+
51
+ #####################################################
52
+ # Address processor and help methods
53
+ #
54
+ # !You have to define the newline method before using this address processor!
55
+
56
+ def cityDivision( address )
57
+ address.suburb || address.ward
58
+ end
59
+
60
+ def adminDivision( address )
61
+ address.state || address.province || address.county
62
+ end
63
+
64
+ def postCode( address )
65
+ address.zip || address.postalCode
66
+ end
67
+
68
+ def formatStandardAddress( address )
69
+ arr = []
70
+ arr << address.street.join( newline ) << newline if address.street
71
+ arr << cityDivision( address ).to_s << newline if cityDivision( address )
72
+ arr << address.city.to_s
73
+ arr << ", #{adminDivision( address )}" if adminDivision( address )
74
+ arr << " #{postCode( address )}" if postCode( address )
75
+ arr << newline
76
+ arr << address.country.to_s
77
+ arr
78
+ end
79
+
80
+ def formatEuropeanAddress( address )
81
+ arr = []
82
+ arr << address.street.join( newline ) << newline if address.street
83
+ arr << cityDivision( address ).to_s << newline if cityDivision( address )
84
+ arr << "#{postCode( address )} " if postCode( address )
85
+ arr << address.city.to_s
86
+ arr << newline << adminDivision( address ).to_s if adminDivision( address )
87
+ arr << newline
88
+ arr << address.country.to_s
89
+ arr
90
+ end
91
+
92
+ def formatItalianAddress( address )
93
+ arr = []
94
+ arr << address.street.join( newline ) << newline if address.street
95
+ arr << "#{address.postalCode} " if address.postalCode
96
+ arr << address.city.to_s
97
+ arr << " (#{address.province})" if address.province
98
+ arr << newline
99
+ arr << address.country.to_s
100
+ arr
101
+ end
102
+
103
+ processor 'address' => 'AddressProcessor' do |address|
104
+ if address.__element.has_elements?
105
+ format = address._format.nil? ? 'standard' : address._format
106
+ send( "format#{format.capitalize}Address", address )
107
+ else
108
+ get_children_value( address, :verbatim => true )
109
+ end
110
+ end
111
+
112
+
113
+ #####################################################
114
+ # Contact processor and help methods
115
+
116
+ def phone_location( location )
117
+ case location
118
+ when 'home' then keyword( :Phone_Home )
119
+ when 'work' then keyword( :Phone_Work )
120
+ when 'mobile' then keyword( :Phone_Mobile )
121
+ else location.nil? ? keyword( :Phone ) : location
122
+ end
123
+ end
124
+
125
+ def fax_location( location )
126
+ case location
127
+ when 'home' then keyword( :Fax_Home )
128
+ when 'work' then keyword( :Fax_Work )
129
+ else location.nil? ? keyword( :Fax ) : location
130
+ end
131
+ end
132
+
133
+ def im_service( service )
134
+ case service
135
+ when 'aim' then keyword( :IM_Aim )
136
+ when 'icq' then keyword( :IM_Icq )
137
+ when 'irc' then keyword( :IM_Irc )
138
+ when 'jabber' then keyword( :IM_Jabber )
139
+ when 'msn' then keyword( :IM_Msn )
140
+ when 'yahoo' then keyword( :IM_Yahoo )
141
+ else service.nil? ? '' : service
142
+ end
143
+ end
144
+
145
+ processor 'contact' => 'ContactProcessor' do |contact|
146
+ arr = []
147
+ arr << contact.phone.collect {|phone| ["#{phone_location(phone._location)}: ", phone] }.join( newline ) if contact.phone
148
+ arr << newline << contact.fax.collect {|fax| ["#{fax_location(fax._location)}: ", fax] }.join( newline ) if contact.fax
149
+ arr << newline << contact.pager.collect {|pager| ["#{keyword(:Pager)}: ", pager] }.join( newline ) if contact.pager
150
+ arr << newline << contact.email.collect {|email| ["#{keyword(:Email)}: ", email] }.join( newline ) if contact.email
151
+ arr << newline << contact.url.collect {|url| ["#{keyword(:Url)}: ", url] }.join( newline ) if contact.url
152
+ arr << newline << contact.instantMessage.collect {|im| ["#{im_service(im._service)}: ", im] }.join( newline ) if contact.instantMessage
153
+ arr
154
+ end