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