/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
 *
50 by edam
updated all URLs, email addresses and package names to ed.am
4
 * Copyright (C) 2009 to 2011 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 );
37 by edam
- updated TODO and NEWS
726
			}
727
		}
728
729
		private boolean doesStringEndInAnEscapeChar( String string )
730
		{
731
			// count the number of backslashes at the end of the string
732
			int count = 0;
733
			for( int a = string.length() - 1; a >= 0; a-- )
734
				if( string.charAt( a ) == '\\' )
735
					count++;
736
				else
737
					break;
738
739
			// if there are an even number of backslashes then the final one
740
			// doesn't count
741
			return ( count & 1 ) == 1;
742
		}
743
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
744
		private String[] splitValueByCharacter( String value, char character )
37 by edam
- updated TODO and NEWS
745
		{
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
746
			// split string in to parts by specified character
37 by edam
- updated TODO and NEWS
747
			ArrayList< String > parts = new ArrayList< String >(
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
748
				Arrays.asList( value.split( "" + character ) ) );
37 by edam
- updated TODO and NEWS
749
750
			// go through parts
751
			for( int a = 0; a < parts.size(); a++ )
752
			{
753
				String str = parts.get( a );
754
93 by Tim Marston
minor style tweaks
755
				// Look for parts that end in an escape character, but ignore
756
				// the final part.  We've already detected escape chars at the
37 by edam
- updated TODO and NEWS
757
				// end of the final part in parseLine() and handled multi-lines
758
				// accordingly.
759
				if( a < parts.size() - 1 &&
760
					doesStringEndInAnEscapeChar( str ) )
761
				{
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
762
					// append the escaped character, join the next part to this
763
					// part and remove the next part
37 by edam
- updated TODO and NEWS
764
					parts.set( a, str.substring( 0, str.length() - 1 ) +
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
765
						character + parts.get( a + 1 ) );
37 by edam
- updated TODO and NEWS
766
					parts.remove( a + 1 );
767
768
					// re-visit this part
769
					a--;
770
					continue;
771
				}
772
773
				// trim and replace string
774
				str = str.trim();
775
				parts.set( a, str );
776
			}
777
778
			String[] ret = new String[ parts.size() ];
779
			return parts.toArray( ret );
1 by edam
Initial import
780
		}
781
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
782
		private String unescapeValue( String value )
783
		{
784
			StringBuilder ret = new StringBuilder( value.length() );
785
			boolean in_escape = false;
786
			for( int a = 0; a < value.length(); a++ )
787
			{
788
				int c = value.codePointAt( a );
789
790
				// process a normal character
791
				if( !in_escape ) {
792
					if( c == '\\' )
793
						in_escape = true;
794
					else
795
						ret.append( Character.toChars( c ) );
796
					continue;
797
				}
798
799
				// process an escape sequence
800
				in_escape = false;
801
				switch( c )
802
				{
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
803
				case 'T':
804
				case 't':
805
					// add tab (invalid/non-standard, but accepted)
806
					ret.append( '\t' );
807
					break;
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
808
				case 'N':
809
				case 'n':
810
					// add newline
811
					ret.append( '\n' );
812
					break;
813
				case '\\':
814
				case ',':
815
				case ';':
816
					// add escaped character
817
					ret.append( Character.toChars( c ) );
818
					break;
819
				default:
820
					// unknown escape sequence, so add it unescaped
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
821
					// (invalid/non-standard, but accepted)
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
822
					ret.append( "\\" );
823
					ret.append( Character.toChars( c ) );
824
					break;
825
				}
826
			}
827
828
			return ret.toString();
829
		}
830
1 by edam
Initial import
831
		private void parseN( String[] params, String value )
832
		{
833
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
834
			if( _name_level >= NAMELEVEL_N ) return;
1 by edam
Initial import
835
836
			// get name parts
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
837
			String[] name_parts = splitValueByCharacter( value, ';' );
1 by edam
Initial import
838
839
			// build name
840
			value = "";
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
841
			final int[] part_order = { 3, 1, 2, 0, 4 };
842
			for( int a = 0; a < part_order.length; a++ )
843
				if( name_parts.length > part_order[ a ] &&
844
					name_parts[ part_order[ a ] ].length() > 0 )
845
				{
846
					// split this part in to it's comma-separated bits
847
					String[] name_part_parts = splitValueByCharacter(
848
						name_parts[ part_order[ a ] ], ',' );
849
					for( int b = 0; b < name_part_parts.length; b++ )
850
						if( name_part_parts[ b ].length() > 0 )
851
						{
77 by edam
fixed some column valuesa dn a broken check to contatinate name parts with spaces
852
							if( value.length() > 0 ) value += " ";
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
853
							value += name_part_parts[ b ];
854
						}
855
				}
1 by edam
Initial import
856
857
			// set name
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
858
			setName( unescapeValue( value ) );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
859
			_name_level = NAMELEVEL_N;
1 by edam
Initial import
860
		}
861
862
		private void parseFN( String[] params, String value )
863
		{
864
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
865
			if( _name_level >= NAMELEVEL_FN ) return;
1 by edam
Initial import
866
867
			// set name
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
868
			setName( unescapeValue( value ) );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
869
			_name_level = NAMELEVEL_FN;
1 by edam
Initial import
870
		}
871
872
		private void parseORG( String[] params, String value )
873
		{
874
			// get org parts
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
875
			String[] org_parts = splitValueByCharacter( value, ';' );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
876
			if( org_parts == null || org_parts.length < 1 ) return;
877
878
			// build organisation name
879
			StringBuilder builder = new StringBuilder(
880
				String.valueOf( org_parts[ 0 ] ) );
881
			for( int a = 1; a < org_parts.length; a++ )
882
				builder.append( ", " ).append( org_parts[ a ] );
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
883
			String organisation = unescapeValue( builder.toString() );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
884
885
			// set organisation name (using a title we've previously found)
886
			addOrganisation( organisation, _cached_title, true );
887
888
			// if we've not previously found a title, store this organisation
889
			// name (we'll need it when we find a title to update the
890
			// organisation, by name), else if we *have* previously found a
891
			// title, clear it (since we just used it)
892
			if( _cached_title == null )
893
				_cached_organisation = organisation;
894
			else
895
				_cached_title = null;
896
		}
897
898
		private void parseTITLE( String[] params, String value )
899
		{
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
900
			value = unescapeValue( value );
901
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
902
			// if we previously had an organisation, look it up and append this
903
			// title to it
904
			if( _cached_organisation != null && hasOrganisations() ) {
905
				HashMap< String, ExtraDetail > datas = getOrganisations();
906
				ExtraDetail detail = datas.get( _cached_organisation );
907
				if( detail != null )
908
					detail.setExtra( value );
909
			}
910
911
			// same as when handling organisation, if we've not previously found
912
			// an organisation we store this title, else we clear it (since we
913
			// just appended this title to it)
914
			if( _cached_organisation == null )
915
				_cached_title = value;
916
			else
917
				_cached_organisation = null;
1 by edam
Initial import
918
		}
919
920
		private void parseTEL( String[] params, String value )
921
		{
922
			if( value.length() == 0 ) return;
923
924
			Set< String > types = extractTypes( params, Arrays.asList(
36 by edam
- formatting: removed some double-indents on overrunning lines
925
				"PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
926
				"PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
1 by edam
Initial import
927
928
			// here's the logic...
41 by edam
- updated TODO
929
			boolean is_preferred = types.contains( "PREF" );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
930
			int type;
1 by edam
Initial import
931
			if( types.contains( "FAX" ) )
932
				if( types.contains( "HOME" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
933
					type = TYPE_FAX_HOME;
1 by edam
Initial import
934
				else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
935
					type = TYPE_FAX_WORK;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
936
			else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
937
				type = TYPE_MOBILE;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
938
			else if( types.contains( "PAGER" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
939
				type = TYPE_PAGER;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
940
			else if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
941
				type = TYPE_WORK;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
942
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
943
				type = TYPE_HOME;
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
944
945
			// add phone number
41 by edam
- updated TODO
946
			addNumber( value, type, is_preferred );
1 by edam
Initial import
947
		}
948
949
		public void parseEMAIL( String[] params, String value )
950
		{
951
			if( value.length() == 0 ) return;
952
953
			Set< String > types = extractTypes( params, Arrays.asList(
36 by edam
- formatting: removed some double-indents on overrunning lines
954
				"PREF", "WORK", "HOME", "INTERNET" ) );
1 by edam
Initial import
955
37 by edam
- updated TODO and NEWS
956
			// add email address
41 by edam
- updated TODO
957
			boolean is_preferred = types.contains( "PREF" );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
958
			int type;
1 by edam
Initial import
959
			if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
960
				type = TYPE_WORK;
1 by edam
Initial import
961
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
962
				type = TYPE_HOME;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
963
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
964
			addEmail( unescapeValue( value ), type, is_preferred );
1 by edam
Initial import
965
		}
966
37 by edam
- updated TODO and NEWS
967
		private void parseADR( String[] params, String value )
968
		{
969
			// get address parts
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
970
			String[] adr_parts = splitValueByCharacter( value, ';' );
37 by edam
- updated TODO and NEWS
971
972
			// build address
973
			value = "";
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
974
			for( int a = 0; a < adr_parts.length; a++ )
975
				if( adr_parts[ a ].length() > 0 )
976
				{
69 by edam
compatibility and flexibility fixes (accept spaces in BEGIN/END lines; accept tabs as folding character; ignore case of item names, encodings and charsets; only parse comma-separated subparts of address parts for v3.0 vcards; accept new-line + white-space folding in v2.1 vCards)
977
					// version 3.0 vCards allow further splitting by comma
978
					if( _version.equals( "3.0" ) )
979
					{
980
						// split this part in to it's comma-separated bits and
981
						// add them on individual lines
982
						String[] adr_part_parts =
983
							splitValueByCharacter( adr_parts[ a ], ',' );
984
						for( int b = 0; b < adr_part_parts.length; b++ )
985
							if( adr_part_parts[ b ].length() > 0 )
986
							{
987
								if( value.length() > 0 ) value += "\n";
988
								value += adr_part_parts[ b ];
989
							}
990
					}
991
					else
992
					{
993
						// add this part on an individual line
994
						if( value.length() > 0 ) value += "\n";
995
						value += adr_parts[ a ];
996
					}
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
997
				}
37 by edam
- updated TODO and NEWS
998
999
			Set< String > types = extractTypes( params, Arrays.asList(
47 by edam
- bump version no. to 1.2
1000
				"PREF", "WORK", "HOME" ) );
1001
1002
			// add address
1003
			int type;
1004
			if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
1005
				type = TYPE_WORK;
47 by edam
- bump version no. to 1.2
1006
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
1007
				type = TYPE_HOME;
47 by edam
- bump version no. to 1.2
1008
1009
			addAddress( unescapeValue( value ), type );
1010
		}
1011
1012
		private void parseLABEL( String[] params, String value )
1013
		{
1014
			Set< String > types = extractTypes( params, Arrays.asList(
1015
				"PREF", "WORK", "HOME" ) );
37 by edam
- updated TODO and NEWS
1016
1017
			// add address
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
1018
			int type;
37 by edam
- updated TODO and NEWS
1019
			if( types.contains( "WORK" ) )
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
1020
				type = TYPE_WORK;
37 by edam
- updated TODO and NEWS
1021
			else
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
1022
				type = TYPE_HOME;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
1023
45 by edam
- fix UTF-8 unencoding by default on v3.0 vCards
1024
			addAddress( unescapeValue( value ), type );
37 by edam
- updated TODO and NEWS
1025
		}
1026
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
1027
		private void parseNOTE( String[] params, String value )
1028
		{
1029
			addNote( unescapeValue( value ) );
1030
		}
1031
43 by edam
- refactored some code to do with how contacts are imported
1032
		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)
1033
			throws ParseException, ContactNotIdentifiableException
1 by edam
Initial import
1034
		{
1035
			// 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)
1036
			if( _version == null && _content_lines != null )
1 by edam
Initial import
1037
				throw new ParseException( R.string.error_vcf_malformed );
1038
43 by edam
- refactored some code to do with how contacts are imported
1039
			// 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)
1040
			finalise();
1 by edam
Initial import
1041
		}
1042
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1043
		/**
1044
		 * Amongst the params, find the value of the first, only, of any with
93 by Tim Marston
minor style tweaks
1045
		 * the specified name.
1046
		 *
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1047
		 * @param params
1048
		 * @param name
1049
		 * @return a value, or null
1050
		 */
1 by edam
Initial import
1051
		private String checkParam( String[] params, String name )
1052
		{
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1053
			String[] res = checkParams( params, name );
1054
			return res.length > 0? res[ 0 ] : null;
1055
		}
1056
1057
		/**
93 by Tim Marston
minor style tweaks
1058
		 * Amongst the params, find the values of any with the specified name.
1059
		 *
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1060
		 * @param params
1061
		 * @param name
1062
		 * @return an array of values, or null
1063
		 */
1064
		private String[] checkParams( String[] params, String name )
1065
		{
1066
			HashSet< String > ret = new HashSet< String >();
1067
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)
1068
			Pattern p = Pattern.compile(
68 by edam
be less sensitive to case when parsing vCard params
1069
				"^" + name + "[ \\t]*=[ \\t]*(\"?)(.*)\\1$",
1070
				Pattern.CASE_INSENSITIVE );
1 by edam
Initial import
1071
			for( int i = 0; i < params.length; i++ ) {
1072
				Matcher m = p.matcher( params[ i ] );
1073
				if( m.matches() )
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1074
					ret.add( m.group( 2 ) );
1 by edam
Initial import
1075
			}
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1076
1077
			return (String[]) ret.toArray( new String[ ret.size() ] );
1 by edam
Initial import
1078
		}
1079
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1080
		/**
93 by Tim Marston
minor style tweaks
1081
		 * Amongst the params, return any type values present.  For v2.1 vCards,
1082
		 * those types are just parameters.  For v3.0, they are prefixed with
1083
		 * "TYPE=".  There may also be multiple type parameters.
1084
		 *
68 by edam
be less sensitive to case when parsing vCard params
1085
		 * @param params an array of params to look for types in
1086
		 * @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
1087
		 * @return a set of present type values
1088
		 */
1 by edam
Initial import
1089
		private Set< String > extractTypes( String[] params,
36 by edam
- formatting: removed some double-indents on overrunning lines
1090
			List< String > valid_types )
1 by edam
Initial import
1091
		{
1092
			HashSet< String > types = new HashSet< String >();
1093
1094
			// get 3.0-style TYPE= param
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1095
			String type_params[] = checkParams( params, "TYPE" );
1096
			for( int a = 0; a < type_params.length; a++ )
1097
			{
73 by edam
handle empty lines in vCards properly; be less case sensitive; fixed some comment typos
1098
				// check for a comma-separated list of types (why? I don't think
1099
				// this is in the specs!)
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
1100
				String[] parts = type_params[ a ].split( "," );
73 by edam
handle empty lines in vCards properly; be less case sensitive; fixed some comment typos
1101
				for( int i = 0; i < parts.length; i++ ) {
1102
					String ucpart = parts[ i ].toUpperCase( Locale.US );
1103
					if( valid_types.contains( ucpart ) )
1104
						types.add( ucpart );
1105
				}
1 by edam
Initial import
1106
			}
1107
1108
			// get 2.1-style type param
1109
			if( _version.equals( "2.1" ) ) {
73 by edam
handle empty lines in vCards properly; be less case sensitive; fixed some comment typos
1110
				for( int i = 1; i < params.length; i++ ) {
1111
					String ucparam = params[ i ].toUpperCase( Locale.US );
1112
					if( valid_types.contains( ucparam ) )
1113
						types.add( ucparam );
1114
				}
1 by edam
Initial import
1115
			}
1116
1117
			return types;
1118
		}
1119
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
1120
		private UnencodeResult unencodeQuotedPrintable( ByteBuffer in )
1 by edam
Initial import
1121
		{
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1122
			boolean another = false;
1123
36 by edam
- formatting: removed some double-indents on overrunning lines
1124
			// 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
1125
			byte[] out = new byte[ in.limit() - in.position() ];
1 by edam
Initial import
1126
			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
1127
			for( int i = in.position(); i < in.limit(); i++ )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1128
			{
1129
				// 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
1130
				byte ch = in.array()[ i ];
1131
				if( ch == '=' && i < in.limit() - 2 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1132
				{
1133
					// 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
1134
					out[ j ] = (byte)(
36 by edam
- formatting: removed some double-indents on overrunning lines
1135
							Character.digit( in.array()[ i + 1 ], 16 ) * 16 +
1136
							Character.digit( in.array()[ i + 2 ], 16 ) );
1 by edam
Initial import
1137
					i += 2;
1138
				}
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
				else if( ch == '=' && i == in.limit() - 1 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1140
				{
1141
					// we found a '=' at the end of a line signifying a multi-
93 by Tim Marston
minor style tweaks
1142
					// line string, so we don't add it
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1143
					another = true;
1144
					continue;
1145
				}
1 by edam
Initial import
1146
				else
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1147
					// 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
1148
					out[ j ] = (byte)ch;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1149
				j++;
1 by edam
Initial import
1150
			}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
1151
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
1152
			return new UnencodeResult( another, ByteBuffer.wrap( out, 0, j ) );
1153
		}
1154
1155
		private ByteBuffer transcodeAsciiToUtf8( ByteBuffer in )
1156
		{
1157
			// transcode
1158
			byte[] out = new byte[ ( in.limit() - in.position() ) * 2 ];
1159
			int j = 0;
1160
			for( int a = in.position(); a < in.limit(); a++ )
1161
			{
1162
				// if char is < 127, keep it as-is
1163
				if( in.array()[ a ] >= 0 )
1164
					out[ j++ ] = in.array()[ a ];
1165
1166
				// else, convert it to UTF-8
1167
				else {
1168
					int b = 0xff & (int)in.array()[ a ];
1169
					out[ j++ ] = (byte)( 0xc0 | ( b >> 6 ) );
1170
					out[ j++ ] = (byte)( 0x80 | ( b & 0x3f ) );
1171
				}
1172
			}
1173
1174
			return ByteBuffer.wrap( out, 0, j );
1 by edam
Initial import
1175
		}
1176
	}
1177
}