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