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