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