/android/import-contacts

To get this branch, use:
bzr branch http://bzr.ed.am/android/import-contacts
6 by edam
- added GPL header comments to all files
1
/*
2
 * VCFImporter.java
3
 *
50 by edam
updated all URLs, email addresses and package names to ed.am
4
 * Copyright (C) 2009 to 2011 Tim Marston <tim@ed.am>
6 by edam
- added GPL header comments to all files
5
 *
6
 * This file is part of the Import Contacts program (hereafter referred
7
 * to as "this program"). For more information, see
50 by edam
updated all URLs, email addresses and package names to ed.am
8
 * http://ed.am/dev/android/import-contacts
6 by edam
- added GPL header comments to all files
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
50 by edam
updated all URLs, email addresses and package names to ed.am
24
package am.ed.importcontacts;
1 by edam
Initial import
25
26
import java.io.BufferedReader;
27
import java.io.File;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
28
import java.io.FileInputStream;
1 by edam
Initial import
29
import java.io.FileNotFoundException;
30
import java.io.FileReader;
31
import java.io.FilenameFilter;
32
import java.io.IOException;
33
import java.io.UnsupportedEncodingException;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
34
import java.nio.ByteBuffer;
37 by edam
- updated TODO and NEWS
35
import java.util.ArrayList;
1 by edam
Initial import
36
import java.util.Arrays;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
37
import java.util.HashMap;
1 by edam
Initial import
38
import java.util.HashSet;
36 by edam
- formatting: removed some double-indents on overrunning lines
39
import java.util.Iterator;
1 by edam
Initial import
40
import java.util.List;
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
41
import java.util.Locale;
37 by edam
- updated TODO and NEWS
42
import java.util.NoSuchElementException;
1 by edam
Initial import
43
import java.util.Set;
44
import java.util.Vector;
45
import java.util.regex.Matcher;
46
import java.util.regex.Pattern;
47
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
48
import android.annotation.SuppressLint;
1 by edam
Initial import
49
import android.content.SharedPreferences;
50
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
51
public class VcardImporter extends Importer
1 by edam
Initial import
52
{
41 by edam
- updated TODO
53
	private int _vcard_count = 0;
1 by edam
Initial import
54
	private int _progress = 0;
55
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
56
	public VcardImporter( Doit doit )
1 by edam
Initial import
57
	{
58
		super( doit );
59
	}
60
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
61
	@SuppressLint( "SdCardPath" )
1 by edam
Initial import
62
	@Override
63
	protected void onImport() throws AbortImportException
64
	{
65
		SharedPreferences prefs = getSharedPreferences();
66
67
		// update UI
68
		setProgressMessage( R.string.doit_scanning );
69
70
		// get a list of vcf files
71
		File[] files = null;
72
		try
73
		{
74
			// open directory
19 by edam
- added file chooser
75
			String path = "/sdcard" + prefs.getString( "location", "/" );
76
			File file = new File( path );
77
			if( !file.exists() )
1 by edam
Initial import
78
				showError( R.string.error_locationnotfound );
79
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
80
			// directory, or file?
19 by edam
- added file chooser
81
			if( file.isDirectory() )
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
82
			{
83
				// get files
84
				class VCardFilter implements FilenameFilter {
85
					public boolean accept( File dir, String name ) {
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
86
						return name.toLowerCase( Locale.US ).endsWith( ".vcf" );
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
87
					}
13 by edam
- converted project to use Android 1.5 SDK
88
				}
19 by edam
- added file chooser
89
				files = file.listFiles( new VCardFilter() );
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
90
			}
91
			else
92
			{
93
				// use just this file
94
				files = new File[ 1 ];
19 by edam
- added file chooser
95
				files[ 0 ] = file;
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
96
			}
1 by edam
Initial import
97
		}
98
		catch( SecurityException e ) {
99
			showError( R.string.error_locationpermissions );
100
		}
101
102
		// check num files and set progress max
103
		if( files != null && files.length > 0 )
104
			setProgressMax( files.length );
105
		else
106
			showError( R.string.error_locationnofiles );
107
108
		// scan through the files
109
		setTmpProgress( 0 );
110
		for( int i = 0; i < files.length; i++ ) {
111
			countVCardFile( files[ i ] );
112
			setTmpProgress( i );
113
		}
41 by edam
- updated TODO
114
		setProgressMax( _vcard_count );	// will also update tmp progress
1 by edam
Initial import
115
116
		// import them
117
		setProgress( 0 );
118
		for( int i = 0; i < files.length; i++ )
119
			importVCardFile( files[ i ] );
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
120
		setProgress( _vcard_count );
1 by edam
Initial import
121
	}
122
123
	private void countVCardFile( File file ) throws AbortImportException
124
	{
125
		try
126
		{
127
			// open file
128
			BufferedReader reader = new BufferedReader(
36 by edam
- formatting: removed some double-indents on overrunning lines
129
				new FileReader( file ) );
1 by edam
Initial import
130
131
			// read
132
			String line;
41 by edam
- updated TODO
133
			boolean in_vcard = false;
1 by edam
Initial import
134
			while( ( line = reader.readLine() ) != null )
135
			{
41 by edam
- updated TODO
136
				if( !in_vcard ) {
1 by edam
Initial import
137
					// look for vcard beginning
36 by edam
- formatting: removed some double-indents on overrunning lines
138
					if( line.matches( "^BEGIN:VCARD" ) ) {
41 by edam
- updated TODO
139
						in_vcard = true;
140
						_vcard_count++;
1 by edam
Initial import
141
					}
142
				}
36 by edam
- formatting: removed some double-indents on overrunning lines
143
				else if( line.matches( "^END:VCARD" ) )
41 by edam
- updated TODO
144
					in_vcard = false;
1 by edam
Initial import
145
			}
146
147
		}
148
		catch( FileNotFoundException e ) {
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
149
			showError( getText( R.string.error_filenotfound ) +
150
				file.getName() );
1 by edam
Initial import
151
		}
152
		catch( IOException e ) {
153
			showError( getText( R.string.error_ioerror ) + file.getName() );
154
		}
155
	}
156
157
	private void importVCardFile( File file ) throws AbortImportException
158
	{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
159
		// check file is good
160
		if( !file.exists() )
161
			showError( getText( R.string.error_filenotfound ) +
162
				file.getName() );
163
		if( file.length() == 0 )
164
			showError( getText( R.string.error_fileisempty ) +
165
				file.getName() );
166
1 by edam
Initial import
167
		try
168
		{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
169
			// open/read file
170
			FileInputStream istream = new FileInputStream( file );
171
			byte[] content = new byte[ (int)file.length() ];
172
			istream.read( content );
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
173
			istream = null;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
174
175
			// import
176
			importVCardFileContent( content, file.getName() );
1 by edam
Initial import
177
		}
178
		catch( FileNotFoundException e ) {
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
179
			showError( getText( R.string.error_filenotfound ) +
180
				file.getName() );
1 by edam
Initial import
181
		}
182
		catch( IOException e ) {
183
			showError( getText( R.string.error_ioerror ) + file.getName() );
184
		}
185
	}
186
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
187
	private void importVCardFileContent( byte[] content, String fileName )
36 by edam
- formatting: removed some double-indents on overrunning lines
188
		throws AbortImportException
1 by edam
Initial import
189
	{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
190
		// go through lines
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
191
		Vcard vcard = null;
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
192
		int vcard_start_line = 0;
36 by edam
- formatting: removed some double-indents on overrunning lines
193
		ContentLineIterator cli = new ContentLineIterator( content );
194
		while( cli.hasNext() )
1 by edam
Initial import
195
		{
36 by edam
- formatting: removed some double-indents on overrunning lines
196
			ByteBuffer buffer = cli.next();
197
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
198
			// get a US-ASCII version of the line for processing
199
			String line;
200
			try {
36 by edam
- formatting: removed some double-indents on overrunning lines
201
				line = new String( buffer.array(), buffer.position(),
202
					buffer.limit() - buffer.position(), "US-ASCII" );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
203
			}
204
			catch( UnsupportedEncodingException e ) {
205
				// we know US-ASCII is supported, so appease the compiler...
206
				line = "";
207
			}
1 by edam
Initial import
208
41 by edam
- updated TODO
209
			if( vcard == null ) {
1 by edam
Initial import
210
				// look for vcard beginning
36 by edam
- formatting: removed some double-indents on overrunning lines
211
				if( line.matches( "^BEGIN:VCARD" ) ) {
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
212
					setProgress( _progress++ );
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
213
					vcard = new Vcard();
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
214
					vcard_start_line = cli.getLineNumber();
1 by edam
Initial import
215
				}
216
			}
217
			else {
218
				// look for vcard content or ending
36 by edam
- formatting: removed some double-indents on overrunning lines
219
				if( line.matches( "^END:VCARD" ) )
1 by edam
Initial import
220
				{
43 by edam
- refactored some code to do with how contacts are imported
221
					// finalise the vcard/contact
1 by edam
Initial import
222
					try {
43 by edam
- refactored some code to do with how contacts are imported
223
						vcard.finaliseVcard();
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
224
225
						// pass the finalised contact to the importer
226
						importContact( vcard );
1 by edam
Initial import
227
					}
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
228
					catch( Vcard.ParseException e ) {
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
229
						if( !showContinue(
230
							getText( R.string.error_vcf_parse ).toString()
231
							+ fileName +
232
							getText( R.string.error_vcf_parse_line ).toString()
233
							+ cli.getLineNumber() + ":\n" + e.getMessage() ) )
234
						{
235
							finish( ACTION_ABORT );
236
						}
237
						else
238
							skipContact();
239
					}
240
					catch( ContactData.ContactNotIdentifiableException e ) {
241
						if( !showContinue(
242
							getText( R.string.error_vcf_parse ).toString()
243
							+ fileName +
244
							getText( R.string.error_vcf_parse_line ).toString()
245
							+ vcard_start_line + ":\n" + getText(
246
								R.string.error_vcf_notenoughinfo ).toString()
247
						) )
248
						{
249
							finish( ACTION_ABORT );
250
						}
251
						else
252
							skipContact();
253
					}
254
255
					// discard this vcard
41 by edam
- updated TODO
256
					vcard = null;
1 by edam
Initial import
257
				}
258
				else
259
				{
260
					// try giving the line to the vcard
261
					try {
41 by edam
- updated TODO
262
						vcard.parseLine( buffer, line,
36 by edam
- formatting: removed some double-indents on overrunning lines
263
							cli.doesNextLineLookFolded() );
1 by edam
Initial import
264
					}
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
265
					catch( Vcard.ParseException e ) {
1 by edam
Initial import
266
						skipContact();
267
						if( !showContinue(
36 by edam
- formatting: removed some double-indents on overrunning lines
268
							getText( R.string.error_vcf_parse ).toString()
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
269
							+ fileName +
270
							getText( R.string.error_vcf_parse_line ).toString()
271
							+ cli.getLineNumber() + "\n" + e.getMessage() ) )
36 by edam
- formatting: removed some double-indents on overrunning lines
272
						{
3 by edam
- added "all done" message
273
							finish( ACTION_ABORT );
36 by edam
- formatting: removed some double-indents on overrunning lines
274
						}
1 by edam
Initial import
275
276
						// although we're continuing, we still need to abort
277
						// this vCard. Further lines will be ignored until we
278
						// get to another BEGIN:VCARD line.
41 by edam
- updated TODO
279
						vcard = null;
1 by edam
Initial import
280
					}
43 by edam
- refactored some code to do with how contacts are imported
281
					catch( Vcard.SkipImportException e ) {
1 by edam
Initial import
282
						skipContact();
283
						// abort this vCard. Further lines will be ignored until
284
						// we get to another BEGIN:VCARD line.
41 by edam
- updated TODO
285
						vcard = null;
1 by edam
Initial import
286
					}
287
				}
288
			}
289
		}
290
	}
291
36 by edam
- formatting: removed some double-indents on overrunning lines
292
	class ContentLineIterator implements Iterator< ByteBuffer >
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
293
	{
36 by edam
- formatting: removed some double-indents on overrunning lines
294
		protected byte[] _content = null;
295
		protected int _pos = 0;
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
296
		protected int _line = 0;
36 by edam
- formatting: removed some double-indents on overrunning lines
297
298
		public ContentLineIterator( byte[] content )
299
		{
300
			_content = content;
301
		}
302
303
		@Override
304
		public boolean hasNext()
305
		{
306
			return _pos < _content.length;
307
		}
308
309
		@Override
310
		public ByteBuffer next()
311
		{
312
			int initial_pos = _pos;
313
314
			// find newline
315
			for( ; _pos < _content.length; _pos++ )
316
				if( _content[ _pos ] == '\n' )
317
				{
318
					// adjust for a \r preceding the \n
319
					int to = ( _pos > 0 && _content[ _pos - 1 ] == '\r' &&
320
						_pos > initial_pos )? _pos - 1 : _pos;
321
					_pos++;
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
322
					_line++;
36 by edam
- formatting: removed some double-indents on overrunning lines
323
					return ByteBuffer.wrap( _content, initial_pos,
324
						to - initial_pos );
325
				}
326
327
			// we didn't find one, but were there bytes left?
328
			if( _pos != initial_pos ) {
329
				int to = _pos;
330
				_pos++;
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
331
				_line++;
36 by edam
- formatting: removed some double-indents on overrunning lines
332
				return ByteBuffer.wrap( _content, initial_pos,
333
					to - initial_pos );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
334
			}
36 by edam
- formatting: removed some double-indents on overrunning lines
335
336
			// no bytes left
337
			throw new NoSuchElementException();
338
		}
339
340
		@Override
341
		public void remove()
342
		{
343
			throw new UnsupportedOperationException();
344
		}
345
346
		/**
347
		 * Does the next line, if there is one, look like it should be folded
348
		 * onto the end of this one?
349
		 * @return
350
		 */
351
		public boolean doesNextLineLookFolded()
352
		{
353
			return _pos > 0 && _pos < _content.length &&
354
				_content[ _pos - 1 ] == '\n' && _content[ _pos ] == ' ';
355
		}
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
356
357
		public int getLineNumber()
358
		{
359
			return _line;
360
		}
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
361
	}
362
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
363
	private class Vcard extends ContactData
1 by edam
Initial import
364
	{
365
		private final static int NAMELEVEL_NONE = 0;
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
366
		private final static int NAMELEVEL_N = 1;
367
		private final static int NAMELEVEL_FN = 2;
1 by edam
Initial import
368
37 by edam
- updated TODO and NEWS
369
		private final static int MULTILINE_NONE = 0;
370
		private final static int MULTILINE_ENCODED = 1;	// v2.1 quoted-printable
371
		private final static int MULTILINE_ESCAPED = 2;	// v2.1 \\CRLF
372
		private final static int MULTILINE_FOLDED = 3;	// v3.0 folding
373
1 by edam
Initial import
374
		private String _version = null;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
375
		private Vector< ByteBuffer > _buffers = null;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
376
		private int _name_level = NAMELEVEL_NONE;
37 by edam
- updated TODO and NEWS
377
		private int _parser_multiline_state = MULTILINE_NONE;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
378
		private String _parser_current_name_and_params = null;
379
		private String _parser_buffered_value_so_far = "";
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
380
		private String _cached_organisation = null;
381
		private String _cached_title = null;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
382
383
		protected class UnencodeResult
384
		{
385
			private boolean _another_line_required;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
386
			private ByteBuffer _buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
387
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
388
			public UnencodeResult( boolean another_line_required,
389
				ByteBuffer buffer )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
390
			{
391
				_another_line_required = another_line_required;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
392
				_buffer = buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
393
			}
394
395
			public boolean isAnotherLineRequired()
396
			{
397
				return _another_line_required;
398
			}
399
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
400
			public ByteBuffer getBuffer()
401
			{
402
				return _buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
403
			}
404
		}
1 by edam
Initial import
405
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
406
		@SuppressWarnings("serial")
1 by edam
Initial import
407
		protected class ParseException extends Exception
408
		{
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
409
			@SuppressWarnings("unused")
1 by edam
Initial import
410
			public ParseException( String error )
411
			{
412
				super( error );
413
			}
414
415
			public ParseException( int res )
416
			{
42 by edam
- renamed VCFImporter to VcardImporter and VCard to Vcard
417
				super( VcardImporter.this.getText( res ).toString() );
1 by edam
Initial import
418
			}
419
		}
420
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
421
		@SuppressWarnings("serial")
43 by edam
- refactored some code to do with how contacts are imported
422
		protected class SkipImportException extends Exception { }
1 by edam
Initial import
423
36 by edam
- formatting: removed some double-indents on overrunning lines
424
		private String extractCollonPartFromLine( ByteBuffer buffer,
425
			String line, boolean former )
426
		{
427
			String ret = null;
428
429
			// get a US-ASCII version of the line for processing, unless we were
430
			// supplied with one
431
			if( line == null ) {
432
				try {
433
					line = new String( buffer.array(), buffer.position(),
434
						buffer.limit() - buffer.position(), "US-ASCII" );
435
				}
436
				catch( UnsupportedEncodingException e ) {
437
					// we know US-ASCII is supported, so appease the compiler...
438
					line = "";
439
				}
440
			}
441
442
			// split line into name and value parts and check to make sure we
443
			// only got 2 parts and that the first part is not zero in length
444
			String[] parts = line.split( ":", 2 );
445
			if( parts.length == 2 && parts[ 0 ].length() > 0 )
446
				ret = parts[ former? 0 : 1 ];
447
448
			return ret;
449
		}
450
451
		private String extractNameAndParamsFromLine( ByteBuffer buffer,
452
			String line )
453
		{
454
			return extractCollonPartFromLine( buffer, line, true );
455
		}
456
457
		private String extractValueFromLine( ByteBuffer buffer, String line )
458
		{
459
			return extractCollonPartFromLine( buffer, line, false );
460
		}
461
462
		public void parseLine( ByteBuffer buffer, String line,
463
			boolean next_line_looks_folded )
43 by edam
- refactored some code to do with how contacts are imported
464
			throws ParseException, SkipImportException,
36 by edam
- formatting: removed some double-indents on overrunning lines
465
			AbortImportException
466
		{
467
			// do we have a version yet?
1 by edam
Initial import
468
			if( _version == null )
469
			{
36 by edam
- formatting: removed some double-indents on overrunning lines
470
				// tentatively get name and params from line
471
				String name_and_params =
472
					extractNameAndParamsFromLine( buffer, line );
473
474
				// is it a version line?
475
				if( name_and_params != null &&
476
					name_and_params.equals( "VERSION" ) )
1 by edam
Initial import
477
				{
36 by edam
- formatting: removed some double-indents on overrunning lines
478
					// yes, get it!
479
					String value = extractValueFromLine( buffer, line );
480
					if( !value.equals( "2.1" ) && !value.equals( "3.0" ) )
1 by edam
Initial import
481
						throw new ParseException( R.string.error_vcf_version );
36 by edam
- formatting: removed some double-indents on overrunning lines
482
					_version = value;
1 by edam
Initial import
483
36 by edam
- formatting: removed some double-indents on overrunning lines
484
					// parse any buffers we've been accumulating while we waited
485
					// for a version
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
486
					if( _buffers != null )
487
						for( int i = 0; i < _buffers.size(); i++ )
36 by edam
- formatting: removed some double-indents on overrunning lines
488
							parseLine( _buffers.get( i ), null,
489
								i + 1 < _buffers.size() &&
490
								_buffers.get( i + 1 ).hasRemaining() &&
491
								_buffers.get( i + 1 ).get(
492
									_buffers.get( i + 1 ).position() ) == ' ' );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
493
					_buffers = null;
1 by edam
Initial import
494
				}
495
				else
496
				{
36 by edam
- formatting: removed some double-indents on overrunning lines
497
					// no, so stash this line till we get a version
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
498
					if( _buffers == null )
499
						_buffers = new Vector< ByteBuffer >();
500
					_buffers.add( buffer );
1 by edam
Initial import
501
				}
502
			}
503
			else
504
			{
36 by edam
- formatting: removed some double-indents on overrunning lines
505
				// name and params and the position in the buffer where the
506
				// "value" part of the line start
507
				String name_and_params;
508
				int pos;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
509
37 by edam
- updated TODO and NEWS
510
				if( _parser_multiline_state != MULTILINE_NONE )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
511
				{
512
					// if we're currently in a multi-line value, use the stored
513
					// property name and parameters
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
514
					name_and_params = _parser_current_name_and_params;
515
37 by edam
- updated TODO and NEWS
516
					// skip some initial line characters, depending on the type
517
					// of multi-line we're handling
36 by edam
- formatting: removed some double-indents on overrunning lines
518
					pos = buffer.position();
37 by edam
- updated TODO and NEWS
519
					switch( _parser_multiline_state )
520
					{
521
					case MULTILINE_FOLDED:
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
522
						pos++;
37 by edam
- updated TODO and NEWS
523
						break;
524
					case MULTILINE_ENCODED:
36 by edam
- formatting: removed some double-indents on overrunning lines
525
						while( pos < buffer.limit() && (
526
							buffer.get( pos ) == ' ' ||
527
							buffer.get( pos ) == '\t' ) )
528
						{
529
							pos++;
530
						}
37 by edam
- updated TODO and NEWS
531
						break;
532
					default:
533
						// do nothing
534
					}
535
536
					// take us out of multi-line so that we can re-detect that
537
					// this line is a multi-line or not
538
					_parser_multiline_state = MULTILINE_NONE;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
539
				}
540
				else
541
				{
36 by edam
- formatting: removed some double-indents on overrunning lines
542
					// get name and params from line, and since we're not
543
					// parsing a subsequent line in a multi-line, this should
544
					// not fail, or it's an error
545
					name_and_params =
546
						extractNameAndParamsFromLine( buffer, line );
547
					if( name_and_params == null )
548
						throw new ParseException(
549
							R.string.error_vcf_malformed );
550
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
551
					// calculate how many chars to skip from beginning of line
552
					// so we skip the property "name:" part
36 by edam
- formatting: removed some double-indents on overrunning lines
553
					pos = buffer.position() + name_and_params.length() + 1;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
554
555
					// reset the saved multi-line state
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
556
					_parser_current_name_and_params = name_and_params;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
557
					_parser_buffered_value_so_far = "";
558
				}
559
36 by edam
- formatting: removed some double-indents on overrunning lines
560
				// get value from buffer, as raw bytes
561
				ByteBuffer value;
562
				value = ByteBuffer.wrap( buffer.array(), pos,
563
					buffer.limit() - pos );
564
1 by edam
Initial import
565
				// get parameter parts
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
566
				String[] name_param_parts = name_and_params.split( ";", -1 );
567
				for( int i = 0; i < name_param_parts.length; i++ )
568
					name_param_parts[ i ] = name_param_parts[ i ].trim();
1 by edam
Initial import
569
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
570
				// determine whether we care about this entry
571
				final HashSet< String > interesting_fields =
47 by edam
- bump version no. to 1.2
572
					new HashSet< String >( Arrays.asList( new String[] { "N",
573
						"FN", "ORG", "TITLE", "TEL", "EMAIL", "ADR", "LABEL" }
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
574
				) );
575
				boolean is_interesting_field =
576
					interesting_fields.contains( name_param_parts[ 0 ] );
577
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
578
				// parse encoding parameter
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
579
				String encoding = checkParam( name_param_parts, "ENCODING" );
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
580
				if( encoding != null )
581
					encoding = encoding.toUpperCase( Locale.US );
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
582
				if( is_interesting_field && encoding != null &&
583
					!encoding.equals( "8BIT" ) &&
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
584
					!encoding.equals( "QUOTED-PRINTABLE" ) )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
585
					//&& !encoding.equals( "BASE64" ) )
586
				{
587
					throw new ParseException( R.string.error_vcf_encoding );
588
				}
589
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
590
				// parse charset parameter
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
591
				String charset = checkParam( name_param_parts, "CHARSET" );
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
592
				if( charset != null )
593
					charset = charset.toUpperCase( Locale.US );
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
594
				if( charset != null &&
595
					!charset.equals( "US-ASCII" ) &&
36 by edam
- formatting: removed some double-indents on overrunning lines
596
					!charset.equals( "ASCII" ) &&
597
					!charset.equals( "UTF-8" ) )
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
598
				{
599
					throw new ParseException( R.string.error_vcf_charset );
600
				}
601
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
602
				// do unencoding (or default to a fake unencoding result with
603
				// the raw string)
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
604
				UnencodeResult unencoding_result = null;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
605
				if( encoding != null && encoding.equals( "QUOTED-PRINTABLE" ) )
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
606
					unencoding_result = unencodeQuotedPrintable( value );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
607
//				else if( encoding != null && encoding.equals( "BASE64" ) )
34 by edam
- check for empty data "values" after parsing line parameters, so that we catch parameter errors (such as unknown encoding types).
608
//					unencoding_result = unencodeBase64( props[ 1 ], charset );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
609
				if( unencoding_result != null ) {
610
					value = unencoding_result.getBuffer();
37 by edam
- updated TODO and NEWS
611
					if( unencoding_result.isAnotherLineRequired() )
612
						_parser_multiline_state = MULTILINE_ENCODED;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
613
				}
614
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
615
				// convert 8-bit US-ASCII charset to UTF-8 (where no charset is
616
				// specified for a v2.1 vcard entry, we assume it's US-ASCII)
617
				if( ( charset == null && _version.equals( "2.1" ) ) ||
618
					( charset != null && (
619
						charset.equals( "ASCII" ) ||
620
						charset.equals( "US-ASCII" ) ) ) )
621
				{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
622
					value = transcodeAsciiToUtf8( value );
623
				}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
624
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
625
				// process charset (value is now in UTF-8)
36 by edam
- formatting: removed some double-indents on overrunning lines
626
				String string_value;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
627
				try {
36 by edam
- formatting: removed some double-indents on overrunning lines
628
					string_value = new String( value.array(), value.position(),
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
629
						value.limit() - value.position(), "UTF-8" );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
630
				} catch( UnsupportedEncodingException e ) {
631
					throw new ParseException( R.string.error_vcf_charset );
632
				}
633
37 by edam
- updated TODO and NEWS
634
				// for some entries that have semicolon-separated value parts,
635
				// check to see if the value ends in an escape character, which
636
				// indicates that we have a multi-line value
637
				if( ( name_param_parts[ 0 ].equals( "N" ) ||
638
					name_param_parts[ 0 ].equals( "ORG" ) ||
639
					name_param_parts[ 0 ].equals( "ADR" ) ) &&
640
					doesStringEndInAnEscapeChar( string_value ) )
641
				{
642
					_parser_multiline_state = MULTILINE_ESCAPED;
643
					string_value = string_value.substring( 0,
644
						string_value.length() - 1 );
645
				}
646
36 by edam
- formatting: removed some double-indents on overrunning lines
647
				// now we know whether we're in an encoding multi-line,
648
				// determine if we're in a v3 folded multi-line or not
37 by edam
- updated TODO and NEWS
649
				if( _parser_multiline_state == MULTILINE_NONE &&
650
					_version.equals( "3.0" ) && next_line_looks_folded )
651
				{
652
					_parser_multiline_state = MULTILINE_FOLDED;
653
				}
36 by edam
- formatting: removed some double-indents on overrunning lines
654
37 by edam
- updated TODO and NEWS
655
				// handle multi-lines by buffering them and parsing them when we
656
				// are processing the last line in a multi-line sequence
657
				if( _parser_multiline_state != MULTILINE_NONE ) {
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
658
					_parser_buffered_value_so_far += string_value;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
659
					return;
660
				}
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
661
				String complete_value =
37 by edam
- updated TODO and NEWS
662
					( _parser_buffered_value_so_far + string_value ).trim();
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
663
34 by edam
- check for empty data "values" after parsing line parameters, so that we catch parameter errors (such as unknown encoding types).
664
				// ignore empty values
665
				if( complete_value.length() < 1 ) return;
666
1 by edam
Initial import
667
				// parse some properties
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
668
				if( name_param_parts[ 0 ].equals( "N" ) )
669
					parseN( name_param_parts, complete_value );
670
				else if( name_param_parts[ 0 ].equals( "FN" ) )
671
					parseFN( name_param_parts, complete_value );
672
				else if( name_param_parts[ 0 ].equals( "ORG" ) )
673
					parseORG( name_param_parts, complete_value );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
674
				else if( name_param_parts[ 0 ].equals( "TITLE" ) )
675
					parseTITLE( name_param_parts, complete_value );
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
676
				else if( name_param_parts[ 0 ].equals( "TEL" ) )
677
					parseTEL( name_param_parts, complete_value );
678
				else if( name_param_parts[ 0 ].equals( "EMAIL" ) )
679
					parseEMAIL( name_param_parts, complete_value );
37 by edam
- updated TODO and NEWS
680
				else if( name_param_parts[ 0 ].equals( "ADR" ) )
681
					parseADR( name_param_parts, complete_value );
47 by edam
- bump version no. to 1.2
682
				else if( name_param_parts[ 0 ].equals( "LABEL" ) )
683
					parseLABEL( name_param_parts, complete_value );
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
684
				else if( name_param_parts[ 0 ].equals( "NOTE" ) )
685
					parseNOTE( name_param_parts, complete_value );
37 by edam
- updated TODO and NEWS
686
			}
687
		}
688
689
		private boolean doesStringEndInAnEscapeChar( String string )
690
		{
691
			// count the number of backslashes at the end of the string
692
			int count = 0;
693
			for( int a = string.length() - 1; a >= 0; a-- )
694
				if( string.charAt( a ) == '\\' )
695
					count++;
696
				else
697
					break;
698
699
			// if there are an even number of backslashes then the final one
700
			// doesn't count
701
			return ( count & 1 ) == 1;
702
		}
703
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
704
		private String[] splitValueByCharacter( String value, char character )
37 by edam
- updated TODO and NEWS
705
		{
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
706
			// split string in to parts by specified character
37 by edam
- updated TODO and NEWS
707
			ArrayList< String > parts = new ArrayList< String >(
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
708
				Arrays.asList( value.split( "" + character ) ) );
37 by edam
- updated TODO and NEWS
709
710
			// go through parts
711
			for( int a = 0; a < parts.size(); a++ )
712
			{
713
				String str = parts.get( a );
714
715
				// look for parts that end in an escape character, but ignore
716
				// the final part. We've already detected escape chars at the
717
				// end of the final part in parseLine() and handled multi-lines
718
				// accordingly.
719
				if( a < parts.size() - 1 &&
720
					doesStringEndInAnEscapeChar( str ) )
721
				{
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
722
					// append the escaped character, join the next part to this
723
					// part and remove the next part
37 by edam
- updated TODO and NEWS
724
					parts.set( a, str.substring( 0, str.length() - 1 ) +
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
725
						character + parts.get( a + 1 ) );
37 by edam
- updated TODO and NEWS
726
					parts.remove( a + 1 );
727
728
					// re-visit this part
729
					a--;
730
					continue;
731
				}
732
733
				// trim and replace string
734
				str = str.trim();
735
				parts.set( a, str );
736
			}
737
738
			String[] ret = new String[ parts.size() ];
739
			return parts.toArray( ret );
1 by edam
Initial import
740
		}
741
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
742
		private String unescapeValue( String value )
743
		{
744
			StringBuilder ret = new StringBuilder( value.length() );
745
			boolean in_escape = false;
746
			for( int a = 0; a < value.length(); a++ )
747
			{
748
				int c = value.codePointAt( a );
749
750
				// process a normal character
751
				if( !in_escape ) {
752
					if( c == '\\' )
753
						in_escape = true;
754
					else
755
						ret.append( Character.toChars( c ) );
756
					continue;
757
				}
758
759
				// process an escape sequence
760
				in_escape = false;
761
				switch( c )
762
				{
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
763
				case 'T':
764
				case 't':
765
					// add tab (invalid/non-standard, but accepted)
766
					ret.append( '\t' );
767
					break;
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
768
				case 'N':
769
				case 'n':
770
					// add newline
771
					ret.append( '\n' );
772
					break;
773
				case '\\':
774
				case ',':
775
				case ';':
776
					// add escaped character
777
					ret.append( Character.toChars( c ) );
778
					break;
779
				default:
780
					// unknown escape sequence, so add it unescaped
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
781
					// (invalid/non-standard, but accepted)
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
782
					ret.append( "\\" );
783
					ret.append( Character.toChars( c ) );
784
					break;
785
				}
786
			}
787
788
			return ret.toString();
789
		}
790
1 by edam
Initial import
791
		private void parseN( String[] params, String value )
792
		{
793
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
794
			if( _name_level >= NAMELEVEL_N ) return;
1 by edam
Initial import
795
796
			// get name parts
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
797
			String[] name_parts = splitValueByCharacter( value, ';' );
1 by edam
Initial import
798
799
			// build name
800
			value = "";
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
801
			final int[] part_order = { 3, 1, 2, 0, 4 };
802
			for( int a = 0; a < part_order.length; a++ )
803
				if( name_parts.length > part_order[ a ] &&
804
					name_parts[ part_order[ a ] ].length() > 0 )
805
				{
806
					// split this part in to it's comma-separated bits
807
					String[] name_part_parts = splitValueByCharacter(
808
						name_parts[ part_order[ a ] ], ',' );
809
					for( int b = 0; b < name_part_parts.length; b++ )
810
						if( name_part_parts[ b ].length() > 0 )
811
						{
812
							if( value.length() == 0 ) value += " ";
813
							value += name_part_parts[ b ];
814
						}
815
				}
1 by edam
Initial import
816
817
			// set name
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
818
			setName( unescapeValue( value ) );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
819
			_name_level = NAMELEVEL_N;
1 by edam
Initial import
820
		}
821
822
		private void parseFN( String[] params, String value )
823
		{
824
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
825
			if( _name_level >= NAMELEVEL_FN ) return;
1 by edam
Initial import
826
827
			// set name
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
828
			setName( unescapeValue( value ) );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
829
			_name_level = NAMELEVEL_FN;
1 by edam
Initial import
830
		}
831
832
		private void parseORG( String[] params, String value )
833
		{
834
			// get org parts
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
835
			String[] org_parts = splitValueByCharacter( value, ';' );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
836
			if( org_parts == null || org_parts.length < 1 ) return;
837
838
			// build organisation name
839
			StringBuilder builder = new StringBuilder(
840
				String.valueOf( org_parts[ 0 ] ) );
841
			for( int a = 1; a < org_parts.length; a++ )
842
				builder.append( ", " ).append( org_parts[ a ] );
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
843
			String organisation = unescapeValue( builder.toString() );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
844
845
			// set organisation name (using a title we've previously found)
846
			addOrganisation( organisation, _cached_title, true );
847
848
			// if we've not previously found a title, store this organisation
849
			// name (we'll need it when we find a title to update the
850
			// organisation, by name), else if we *have* previously found a
851
			// title, clear it (since we just used it)
852
			if( _cached_title == null )
853
				_cached_organisation = organisation;
854
			else
855
				_cached_title = null;
856
		}
857
858
		private void parseTITLE( String[] params, String value )
859
		{
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
860
			value = unescapeValue( value );
861
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
862
			// if we previously had an organisation, look it up and append this
863
			// title to it
864
			if( _cached_organisation != null && hasOrganisations() ) {
865
				HashMap< String, ExtraDetail > datas = getOrganisations();
866
				ExtraDetail detail = datas.get( _cached_organisation );
867
				if( detail != null )
868
					detail.setExtra( value );
869
			}
870
871
			// same as when handling organisation, if we've not previously found
872
			// an organisation we store this title, else we clear it (since we
873
			// just appended this title to it)
874
			if( _cached_organisation == null )
875
				_cached_title = value;
876
			else
877
				_cached_organisation = null;
1 by edam
Initial import
878
		}
879
880
		private void parseTEL( String[] params, String value )
881
		{
882
			if( value.length() == 0 ) return;
883
884
			Set< String > types = extractTypes( params, Arrays.asList(
36 by edam
- formatting: removed some double-indents on overrunning lines
885
				"PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
886
				"PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
1 by edam
Initial import
887
888
			// here's the logic...
41 by edam
- updated TODO
889
			boolean is_preferred = types.contains( "PREF" );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
890
			int type;
1 by edam
Initial import
891
			if( types.contains( "FAX" ) )
892
				if( types.contains( "HOME" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
893
					type = TYPE_FAX_HOME;
1 by edam
Initial import
894
				else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
895
					type = TYPE_FAX_WORK;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
896
			else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
897
				type = TYPE_MOBILE;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
898
			else if( types.contains( "PAGER" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
899
				type = TYPE_PAGER;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
900
			else if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
901
				type = TYPE_WORK;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
902
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
903
				type = TYPE_HOME;
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
904
905
			// add phone number
41 by edam
- updated TODO
906
			addNumber( value, type, is_preferred );
1 by edam
Initial import
907
		}
908
909
		public void parseEMAIL( String[] params, String value )
910
		{
911
			if( value.length() == 0 ) return;
912
913
			Set< String > types = extractTypes( params, Arrays.asList(
36 by edam
- formatting: removed some double-indents on overrunning lines
914
				"PREF", "WORK", "HOME", "INTERNET" ) );
1 by edam
Initial import
915
37 by edam
- updated TODO and NEWS
916
			// add email address
41 by edam
- updated TODO
917
			boolean is_preferred = types.contains( "PREF" );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
918
			int type;
1 by edam
Initial import
919
			if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
920
				type = TYPE_WORK;
1 by edam
Initial import
921
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
922
				type = TYPE_HOME;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
923
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
924
			addEmail( unescapeValue( value ), type, is_preferred );
1 by edam
Initial import
925
		}
926
37 by edam
- updated TODO and NEWS
927
		private void parseADR( String[] params, String value )
928
		{
929
			// get address parts
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
930
			String[] adr_parts = splitValueByCharacter( value, ';' );
37 by edam
- updated TODO and NEWS
931
932
			// build address
933
			value = "";
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
934
			for( int a = 0; a < adr_parts.length; a++ )
935
				if( adr_parts[ a ].length() > 0 )
936
				{
937
					// split this part in to it's comma-separated bits
938
					String[] adr_part_parts =
939
						splitValueByCharacter( adr_parts[ a ], ',' );
940
					for( int b = 0; b < adr_part_parts.length; b++ )
941
						if( adr_part_parts[ b ].length() > 0 )
942
						{
943
							if( value.length() > 0 ) value += "\n";
944
							value += adr_part_parts[ b ];
945
						}
946
				}
37 by edam
- updated TODO and NEWS
947
948
			Set< String > types = extractTypes( params, Arrays.asList(
47 by edam
- bump version no. to 1.2
949
				"PREF", "WORK", "HOME" ) );
950
951
			// add address
952
			int type;
953
			if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
954
				type = TYPE_WORK;
47 by edam
- bump version no. to 1.2
955
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
956
				type = TYPE_HOME;
47 by edam
- bump version no. to 1.2
957
958
			addAddress( unescapeValue( value ), type );
959
		}
960
961
		private void parseLABEL( String[] params, String value )
962
		{
963
			Set< String > types = extractTypes( params, Arrays.asList(
964
				"PREF", "WORK", "HOME" ) );
37 by edam
- updated TODO and NEWS
965
966
			// add address
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
967
			int type;
37 by edam
- updated TODO and NEWS
968
			if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
969
				type = TYPE_WORK;
37 by edam
- updated TODO and NEWS
970
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
971
				type = TYPE_HOME;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
972
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
973
			addAddress( unescapeValue( value ), type );
37 by edam
- updated TODO and NEWS
974
		}
975
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
976
		private void parseNOTE( String[] params, String value )
977
		{
978
			addNote( unescapeValue( value ) );
979
		}
980
43 by edam
- refactored some code to do with how contacts are imported
981
		public void finaliseVcard()
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
982
			throws ParseException, ContactNotIdentifiableException
1 by edam
Initial import
983
		{
984
			// missing version (and data is present)
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
985
			if( _version == null && _buffers != null )
1 by edam
Initial import
986
				throw new ParseException( R.string.error_vcf_malformed );
987
43 by edam
- refactored some code to do with how contacts are imported
988
			// finalise the parent class
44 by edam
- added checks for Doit.this == null when handling dialog buttons (I managed to abort an import as a duplicate contacts dialog was shown, but can't reproduce it now)
989
			finalise();
1 by edam
Initial import
990
		}
991
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
992
		/**
993
		 * Amongst the params, find the value of the first, only, of any with
994
		 * the specified name
995
		 * @param params
996
		 * @param name
997
		 * @return a value, or null
998
		 */
1 by edam
Initial import
999
		private String checkParam( String[] params, String name )
1000
		{
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1001
			String[] res = checkParams( params, name );
1002
			return res.length > 0? res[ 0 ] : null;
1003
		}
1004
1005
		/**
1006
		 * Amongst the params, find the values of any with the specified name
1007
		 * @param params
1008
		 * @param name
1009
		 * @return an array of values, or null
1010
		 */
1011
		private String[] checkParams( String[] params, String name )
1012
		{
1013
			HashSet< String > ret = new HashSet< String >();
1014
35 by edam
- accept parameters that are quoted (this doesn't appear to be part of the standards AFAICT, but Evolution apparently quotes parameter values)
1015
			Pattern p = Pattern.compile(
68 by edam
be less sensitive to case when parsing vCard params
1016
				"^" + name + "[ \\t]*=[ \\t]*(\"?)(.*)\\1$",
1017
				Pattern.CASE_INSENSITIVE );
1 by edam
Initial import
1018
			for( int i = 0; i < params.length; i++ ) {
1019
				Matcher m = p.matcher( params[ i ] );
1020
				if( m.matches() )
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1021
					ret.add( m.group( 2 ) );
1 by edam
Initial import
1022
			}
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1023
1024
			return (String[]) ret.toArray( new String[ ret.size() ] );
1 by edam
Initial import
1025
		}
1026
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1027
		/**
1028
		 * Amongst the params, return any type values present. For v2.1 vCards,
1029
		 * those types are just parameters. For v3.0, they are prefixed with
1030
		 * "TYPE=". There may also be multiple type parameters.
68 by edam
be less sensitive to case when parsing vCard params
1031
		 * @param params an array of params to look for types in
1032
		 * @param valid_types an list of upper-case type values to look for
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1033
		 * @return a set of present type values
1034
		 */
1 by edam
Initial import
1035
		private Set< String > extractTypes( String[] params,
36 by edam
- formatting: removed some double-indents on overrunning lines
1036
			List< String > valid_types )
1 by edam
Initial import
1037
		{
1038
			HashSet< String > types = new HashSet< String >();
1039
1040
			// get 3.0-style TYPE= param
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1041
			String type_params[] = checkParams( params, "TYPE" );
1042
			for( int a = 0; a < type_params.length; a++ )
1043
			{
1044
				// check for a comma-separated list of types (why? this isn't in
1045
				// the specs!)
1046
				String[] parts = type_params[ a ].split( "," );
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
1047
				for( int i = 0; i < parts.length; i++ )
68 by edam
be less sensitive to case when parsing vCard params
1048
					parts[ i ] = parts[ i ].toUpperCase( Locale.US );
1049
				for( int i = 0; i < parts.length; i++ )
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
1050
					if( valid_types.contains( parts[ i ] ) )
1051
						types.add( parts[ i ] );
1 by edam
Initial import
1052
			}
1053
1054
			// get 2.1-style type param
1055
			if( _version.equals( "2.1" ) ) {
1056
				for( int i = 1; i < params.length; i++ )
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
1057
					if( valid_types.contains( params[ i ] ) )
1 by edam
Initial import
1058
						types.add( params[ i ] );
1059
			}
1060
1061
			return types;
1062
		}
1063
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1064
		private UnencodeResult unencodeQuotedPrintable( ByteBuffer in )
1 by edam
Initial import
1065
		{
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1066
			boolean another = false;
1067
36 by edam
- formatting: removed some double-indents on overrunning lines
1068
			// unencode quoted-printable encoding, as per RFC1521 section 5.1
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1069
			byte[] out = new byte[ in.limit() - in.position() ];
1 by edam
Initial import
1070
			int j = 0;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1071
			for( int i = in.position(); i < in.limit(); i++ )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1072
			{
1073
				// get next char and process...
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1074
				byte ch = in.array()[ i ];
1075
				if( ch == '=' && i < in.limit() - 2 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1076
				{
1077
					// we found a =XX format byte, add it
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1078
					out[ j ] = (byte)(
36 by edam
- formatting: removed some double-indents on overrunning lines
1079
							Character.digit( in.array()[ i + 1 ], 16 ) * 16 +
1080
							Character.digit( in.array()[ i + 2 ], 16 ) );
1 by edam
Initial import
1081
					i += 2;
1082
				}
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1083
				else if( ch == '=' && i == in.limit() - 1 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1084
				{
1085
					// we found a '=' at the end of a line signifying a multi-
1086
					// line string, so we don't add it.
1087
					another = true;
1088
					continue;
1089
				}
1 by edam
Initial import
1090
				else
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1091
					// just a normal char...
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1092
					out[ j ] = (byte)ch;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1093
				j++;
1 by edam
Initial import
1094
			}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1095
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
1096
			return new UnencodeResult( another, ByteBuffer.wrap( out, 0, j ) );
1097
		}
1098
1099
		private ByteBuffer transcodeAsciiToUtf8( ByteBuffer in )
1100
		{
1101
			// transcode
1102
			byte[] out = new byte[ ( in.limit() - in.position() ) * 2 ];
1103
			int j = 0;
1104
			for( int a = in.position(); a < in.limit(); a++ )
1105
			{
1106
				// if char is < 127, keep it as-is
1107
				if( in.array()[ a ] >= 0 )
1108
					out[ j++ ] = in.array()[ a ];
1109
1110
				// else, convert it to UTF-8
1111
				else {
1112
					int b = 0xff & (int)in.array()[ a ];
1113
					out[ j++ ] = (byte)( 0xc0 | ( b >> 6 ) );
1114
					out[ j++ ] = (byte)( 0x80 | ( b & 0x3f ) );
1115
				}
1116
			}
1117
1118
			return ByteBuffer.wrap( out, 0, j );
1 by edam
Initial import
1119
		}
1120
	}
1121
}