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