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