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