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