1/*
2 This file is part of libkabc.
3 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "address.h"
22
23#include <krandom.h>
24#include <kdebug.h>
25#include <kglobal.h>
26#include <klocale.h>
27#include <klocalizedstring.h>
28#include <kconfig.h>
29#include <kstandarddirs.h>
30#include <kconfiggroup.h>
31
32#include <QtCore/QFile>
33#include <QtCore/QMap>
34#include <QtCore/QTextStream>
35#include <QtCore/QSharedData>
36
37using namespace KABC;
38
39// template tags for address formatting localization
40#define KABC_FMTTAG_realname QString::fromLatin1("%n")
41#define KABC_FMTTAG_REALNAME QString::fromLatin1("%N")
42#define KABC_FMTTAG_company QString::fromLatin1("%cm")
43#define KABC_FMTTAG_COMPANY QString::fromLatin1("%CM")
44#define KABC_FMTTAG_pobox QString::fromLatin1("%p")
45#define KABC_FMTTAG_street QString::fromLatin1("%s")
46#define KABC_FMTTAG_STREET QString::fromLatin1("%S")
47#define KABC_FMTTAG_zipcode QString::fromLatin1("%z")
48#define KABC_FMTTAG_location QString::fromLatin1("%l")
49#define KABC_FMTTAG_LOCATION QString::fromLatin1("%L")
50#define KABC_FMTTAG_region QString::fromLatin1("%r")
51#define KABC_FMTTAG_REGION QString::fromLatin1("%R")
52#define KABC_FMTTAG_newline QString::fromLatin1("\\n")
53#define KABC_FMTTAG_condcomma QString::fromLatin1("%,")
54#define KABC_FMTTAG_condwhite QString::fromLatin1("%w")
55#define KABC_FMTTAG_purgeempty QString::fromLatin1("%0")
56
57/**
58 Finds the balanced closing bracket starting from the opening bracket at
59 pos in tsection.
60 @return position of closing bracket, -1 for unbalanced brackets
61*/
62static int findBalancedBracket( const QString &tsection, int pos )
63{
64 int balancecounter = 0;
65 for ( int i = pos + 1; i < tsection.length(); ++i ) {
66 if ( QLatin1Char( ')' ) == tsection[i] && 0 == balancecounter ) {
67 // found end of brackets
68 return i;
69 } else {
70 if ( QLatin1Char( '(' ) == tsection[i] ) {
71 // nested brackets
72 balancecounter++;
73 }
74 }
75 }
76 return -1;
77}
78
79/**
80 Parses a snippet of an address template
81 @param tsection the template string to be parsed
82 @param result QString reference in which the result will be stored
83 @return true if at least one tag evaluated positively, else false
84*/
85static bool parseAddressTemplateSection( const QString &tsection, QString &result,
86 const QString &realName, const QString &orgaName,
87 const KABC::Address &address )
88{
89 // This method first parses and substitutes any bracketed sections and
90 // after that replaces any tags with their values. If a bracketed section
91 // or a tag evaluate to zero, they are not just removed but replaced
92 // with a placeholder. This is because in the last step conditionals are
93 // resolved which depend on information about zero-evaluations.
94 result = tsection;
95 int stpos = 0;
96 bool ret = false;
97
98 // first check for brackets that have to be evaluated first
99 int fpos = result.indexOf( KABC_FMTTAG_purgeempty, stpos );
100 while ( -1 != fpos ) {
101 int bpos1 = fpos + KABC_FMTTAG_purgeempty.length();
102 int bpos2;
103 // expect opening bracket and find next balanced closing bracket. If
104 // next char is no opening bracket, continue parsing (no valid tag)
105 if ( QLatin1Char( '(' ) == result[bpos1] ) {
106 bpos2 = findBalancedBracket( result, bpos1 );
107 if ( -1 != bpos2 ) {
108 // we have balanced brackets, recursively parse:
109 QString rplstr;
110 bool purge = !parseAddressTemplateSection( result.mid( bpos1+1,
111 bpos2-bpos1-1 ), rplstr,
112 realName, orgaName, address );
113 if ( purge ) {
114 // purge -> remove all
115 // replace with !_P_!, so conditional tags work later
116 result.replace( fpos, bpos2 - fpos + 1, QLatin1String( "!_P_!" ) );
117 // leave stpos as it is
118 } else {
119 // no purge -> replace with recursively parsed string
120 result.replace( fpos, bpos2 - fpos + 1, rplstr );
121 ret = true;
122 stpos = fpos + rplstr.length();
123 }
124 } else {
125 // unbalanced brackets: keep on parsing (should not happen
126 // and will result in bad formatting)
127 stpos = bpos1;
128 }
129 }
130 fpos = result.indexOf( KABC_FMTTAG_purgeempty, stpos );
131 }
132
133 // after sorting out all purge tags, we just search'n'replace the rest,
134 // keeping track of whether at least one tag evaluates to something.
135 // The following macro needs QString for R_FIELD
136 // It substitutes !_P_! for empty fields so conditional tags work later
137#define REPLTAG(R_TAG,R_FIELD) \
138 if ( result.indexOf( R_TAG, false ) != -1 ) { \
139 QString rpl = R_FIELD.isEmpty() ? QLatin1String( "!_P_!" ) : R_FIELD; \
140 result.replace( R_TAG, rpl ); \
141 if ( !R_FIELD.isEmpty() ) { \
142 ret = true; \
143 } \
144 }
145 REPLTAG( KABC_FMTTAG_realname, realName );
146 REPLTAG( KABC_FMTTAG_REALNAME, realName.toUpper() );
147 REPLTAG( KABC_FMTTAG_company, orgaName );
148 REPLTAG( KABC_FMTTAG_COMPANY, orgaName.toUpper() );
149 REPLTAG( KABC_FMTTAG_pobox, address.postOfficeBox() );
150 REPLTAG( KABC_FMTTAG_street, address.street() );
151 REPLTAG( KABC_FMTTAG_STREET, address.street().toUpper() );
152 REPLTAG( KABC_FMTTAG_zipcode, address.postalCode() );
153 REPLTAG( KABC_FMTTAG_location, address.locality() );
154 REPLTAG( KABC_FMTTAG_LOCATION, address.locality().toUpper() );
155 REPLTAG( KABC_FMTTAG_region, address.region() );
156 REPLTAG( KABC_FMTTAG_REGION, address.region().toUpper() );
157 result.replace( KABC_FMTTAG_newline, QLatin1String( "\n" ) );
158#undef REPLTAG
159
160 // conditional comma
161 fpos = result.indexOf( KABC_FMTTAG_condcomma, 0 );
162 while ( -1 != fpos ) {
163 const QString str1 = result.mid( fpos - 5, 5 );
164 const QString str2 = result.mid( fpos + 2, 5 );
165 if ( str1 != QLatin1String( "!_P_!" ) && str2 != QLatin1String( "!_P_!" ) ) {
166 result.replace( fpos, 2, QLatin1String( ", " ) );
167 } else {
168 result.remove( fpos, 2 );
169 }
170 fpos = result.indexOf( KABC_FMTTAG_condcomma, fpos );
171 }
172 // conditional whitespace
173 fpos = result.indexOf( KABC_FMTTAG_condwhite, 0 );
174 while ( -1 != fpos ) {
175 const QString str1 = result.mid( fpos - 5, 5 );
176 const QString str2 = result.mid( fpos + 2, 5 );
177 if ( str1 != QLatin1String( "!_P_!" ) && str2 != QLatin1String( "!_P_!" ) ) {
178 result.replace( fpos, 2, QLatin1String( " " ) );
179 } else {
180 result.remove( fpos, 2 );
181 }
182 fpos = result.indexOf( KABC_FMTTAG_condwhite, fpos );
183 }
184
185 // remove purged:
186 result.remove( QLatin1String( "!_P_!" ) );
187
188 return ret;
189}
190
191class Address::Private : public QSharedData
192{
193 public:
194 Private()
195 : mEmpty( true ), mType( 0 )
196 {
197 mId = KRandom::randomString( 10 );
198 }
199
200 Private( const Private &other )
201 : QSharedData( other )
202 {
203 mEmpty = other.mEmpty;
204 mId = other.mId;
205 mType = other.mType;
206
207 mPostOfficeBox = other.mPostOfficeBox;
208 mExtended = other.mExtended;
209 mStreet = other.mStreet;
210 mLocality = other.mLocality;
211 mRegion = other.mRegion;
212 mPostalCode = other.mPostalCode;
213 mCountry = other.mCountry;
214 mLabel = other.mLabel;
215 }
216
217 bool mEmpty;
218 QString mId;
219 Type mType;
220
221 QString mPostOfficeBox;
222 QString mExtended;
223 QString mStreet;
224 QString mLocality;
225 QString mRegion;
226 QString mPostalCode;
227 QString mCountry;
228 QString mLabel;
229};
230
231Address::Address()
232 : d( new Private )
233{
234}
235
236Address::Address( Type type )
237 : d( new Private )
238{
239 d->mType = type;
240}
241
242Address::Address( const Address &other )
243 : d( other.d )
244{
245}
246
247Address::~Address()
248{
249}
250
251Address &Address::operator=( const Address &other )
252{
253 if ( this != &other ) {
254 d = other.d;
255 }
256
257 return *this;
258}
259
260bool Address::operator==( const Address &other ) const
261{
262 if ( d->mId != other.d->mId ) {
263 return false;
264 }
265 if ( d->mType != other.d->mType ) {
266 return false;
267 }
268 if ( d->mPostOfficeBox != other.d->mPostOfficeBox ) {
269 return false;
270 }
271 if ( d->mExtended != other.d->mExtended ) {
272 return false;
273 }
274 if ( d->mStreet != other.d->mStreet ) {
275 return false;
276 }
277 if ( d->mLocality != other.d->mLocality ) {
278 return false;
279 }
280 if ( d->mRegion != other.d->mRegion ) {
281 return false;
282 }
283 if ( d->mPostalCode != other.d->mPostalCode ) {
284 return false;
285 }
286 if ( d->mCountry != other.d->mCountry ) {
287 return false;
288 }
289 if ( d->mLabel != other.d->mLabel ) {
290 return false;
291 }
292
293 return true;
294}
295
296bool Address::operator!=( const Address &a ) const
297{
298 return !( a == *this );
299}
300
301bool Address::isEmpty() const
302{
303 return d->mEmpty;
304}
305
306void Address::clear()
307{
308 *this = Address();
309}
310
311void Address::setId( const QString &id )
312{
313 d->mEmpty = false;
314 d->mId = id;
315}
316
317QString Address::id() const
318{
319 return d->mId;
320}
321
322void Address::setType( Type type )
323{
324 d->mEmpty = false;
325 d->mType = type;
326}
327
328Address::Type Address::type() const
329{
330 return d->mType;
331}
332
333QString Address::typeLabel() const
334{
335 QString label;
336 bool first = true;
337
338 const TypeList list = typeList();
339
340 TypeList::ConstIterator it;
341 for ( it = list.begin(); it != list.end(); ++it ) {
342 if ( ( type() & ( *it ) ) && ( ( *it ) != Pref ) ) {
343 if ( !first ) {
344 label.append( QLatin1Char( '/' ) );
345 }
346 label.append( typeLabel( *it ) );
347 if ( first ) {
348 first = false;
349 }
350 }
351 }
352
353 return label;
354}
355
356void Address::setPostOfficeBox( const QString &postOfficeBox )
357{
358 d->mEmpty = false;
359 d->mPostOfficeBox = postOfficeBox;
360}
361
362QString Address::postOfficeBox() const
363{
364 return d->mPostOfficeBox;
365}
366
367QString Address::postOfficeBoxLabel()
368{
369 return i18n( "Post Office Box" );
370}
371
372void Address::setExtended( const QString &extended )
373{
374 d->mEmpty = false;
375 d->mExtended = extended;
376}
377
378QString Address::extended() const
379{
380 return d->mExtended;
381}
382
383QString Address::extendedLabel()
384{
385 return i18n( "Extended Address Information" );
386}
387
388void Address::setStreet( const QString &street )
389{
390 d->mEmpty = false;
391 d->mStreet = street;
392}
393
394QString Address::street() const
395{
396 return d->mStreet;
397}
398
399QString Address::streetLabel()
400{
401 return i18n( "Street" );
402}
403
404void Address::setLocality( const QString &locality )
405{
406 d->mEmpty = false;
407 d->mLocality = locality;
408}
409
410QString Address::locality() const
411{
412 return d->mLocality;
413}
414
415QString Address::localityLabel()
416{
417 return i18n( "Locality" );
418}
419
420void Address::setRegion( const QString &region )
421{
422 d->mEmpty = false;
423 d->mRegion = region;
424}
425
426QString Address::region() const
427{
428 return d->mRegion;
429}
430
431QString Address::regionLabel()
432{
433 return i18n( "Region" );
434}
435
436void Address::setPostalCode( const QString &postalCode )
437{
438 d->mEmpty = false;
439 d->mPostalCode = postalCode;
440}
441
442QString Address::postalCode() const
443{
444 return d->mPostalCode;
445}
446
447QString Address::postalCodeLabel()
448{
449 return i18n( "Postal Code" );
450}
451
452void Address::setCountry( const QString &country )
453{
454 d->mEmpty = false;
455 d->mCountry = country;
456}
457
458QString Address::country() const
459{
460 return d->mCountry;
461}
462
463QString Address::countryLabel()
464{
465 return i18n( "Country" );
466}
467
468void Address::setLabel( const QString &label )
469{
470 d->mEmpty = false;
471 d->mLabel = label;
472}
473
474QString Address::label() const
475{
476 return d->mLabel;
477}
478
479QString Address::labelLabel()
480{
481 return i18n( "Delivery Label" );
482}
483
484Address::TypeList Address::typeList()
485{
486 static TypeList list;
487
488 if ( list.isEmpty() ) {
489 list << Dom << Intl << Postal << Parcel << Home << Work << Pref;
490 }
491
492 return list;
493}
494
495QString Address::typeLabel( Type type )
496{
497 if ( type & Pref ) {
498 return i18nc( "Preferred address", "Preferred" );
499 }
500
501 switch ( type ) {
502 case Dom:
503 return i18nc( "Address is in home country", "Domestic" );
504 break;
505 case Intl:
506 return i18nc( "Address is not in home country", "International" );
507 break;
508 case Postal:
509 return i18nc( "Address for delivering letters", "Postal" );
510 break;
511 case Parcel:
512 return i18nc( "Address for delivering packages", "Parcel" );
513 break;
514 case Home:
515 return i18nc( "Home Address", "Home" );
516 break;
517 case Work:
518 return i18nc( "Work Address", "Work" );
519 break;
520 case Pref:
521 return i18n( "Preferred Address" );
522 break;
523 default:
524 return i18nc( "another type of address", "Other" );
525 break;
526 }
527}
528
529QString Address::toString() const
530{
531 QString str;
532
533 str += QLatin1String( "Address {\n" );
534 str += QString::fromLatin1( " IsEmpty: %1\n" ).
535 arg( d->mEmpty ? QLatin1String( "true" ) : QLatin1String( "false" ) );
536 str += QString::fromLatin1( " Id: %1\n" ).arg( d->mId );
537 str += QString::fromLatin1( " Type: %1\n" ).arg( typeLabel( d->mType ) );
538 str += QString::fromLatin1( " Post office box: %1\n" ).arg( d->mPostOfficeBox );
539 str += QString::fromLatin1( " Extended: %1\n" ).arg( d->mExtended );
540 str += QString::fromLatin1( " Street: %1\n" ).arg( d->mStreet );
541 str += QString::fromLatin1( " Locality: %1\n" ).arg( d->mLocality );
542 str += QString::fromLatin1( " Region: %1\n" ).arg( d->mRegion );
543 str += QString::fromLatin1( " Postal code: %1\n" ).arg( d->mPostalCode );
544 str += QString::fromLatin1( " Country: %1\n" ).arg( d->mCountry );
545 str += QString::fromLatin1( " Label: %1\n" ).arg( d->mLabel );
546 str += QLatin1String( "}\n" );
547
548 return str;
549}
550
551QString Address::formattedAddress( const QString &realName,
552 const QString &orgaName ) const
553{
554 QString ciso;
555 QString addrTemplate;
556 QString ret;
557
558 // FIXME: first check for iso-country-field and prefer that one
559 if ( !country().isEmpty() ) {
560 ciso = countryToISO( country() );
561 } else {
562 // fall back to our own country
563 ciso = KGlobal::locale()->country();
564 }
565 KConfig entry( KStandardDirs::locate( "locale",
566 QLatin1String( "l10n/" ) + ciso + QLatin1String( "/entry.desktop" ) ) );
567
568 KConfigGroup group = entry.group( "KCM Locale" );
569 // decide whether this needs special business address formatting
570 if ( orgaName.isEmpty() ) {
571 addrTemplate = group.readEntry( "AddressFormat" );
572 } else {
573 addrTemplate = group.readEntry( "BusinessAddressFormat" );
574 if ( addrTemplate.isEmpty() ) {
575 addrTemplate = group.readEntry( "AddressFormat" );
576 }
577 }
578
579 // in the case there's no format found at all, default to what we've always
580 // used:
581 if ( addrTemplate.isEmpty() ) {
582 kWarning( 5700 ) << "address format database incomplete"
583 << "(no format for locale" << ciso
584 << "found). Using default address formatting.";
585 addrTemplate = QLatin1String( "%0(%n\\n)%0(%cm\\n)%0(%s\\n)%0(PO BOX %p\\n)%0(%l%w%r)%,%z" );
586 }
587
588 // scan
589 parseAddressTemplateSection( addrTemplate, ret, realName, orgaName, *this );
590
591 // now add the country line if needed (formatting this time according to
592 // the rules of our own system country )
593 if ( !country().isEmpty() ) {
594 KConfig entry( KStandardDirs::locate( "locale", QLatin1String( "l10n/" ) +
595 KGlobal::locale()->country() +
596 QLatin1String( "/entry.desktop" ) ) );
597 KConfigGroup group = entry.group( "KCM Locale" );
598 QString cpos = group.readEntry( "AddressCountryPosition" );
599 if ( QLatin1String( "BELOW" ) == cpos || cpos.isEmpty() ) {
600 ret = ret + QLatin1String( "\n\n" ) + country().toUpper();
601 } else if ( QLatin1String( "below" ) == cpos ) {
602 ret = ret + QLatin1String( "\n\n" ) + country();
603 } else if ( QLatin1String( "ABOVE" ) == cpos ) {
604 ret = country().toUpper() + QLatin1String( "\n\n" ) + ret;
605 } else if ( QLatin1String( "above" ) == cpos ) {
606 ret = country() + QLatin1String( "\n\n" ) + ret;
607 }
608 }
609
610 return ret;
611}
612
613QString Address::countryToISO( const QString &cname )
614{
615 // we search a map file for translations from country names to
616 // iso codes, storing caching things in a QMap for faster future
617 // access.
618 typedef QMap<QString, QString> stringMap;
619 K_GLOBAL_STATIC( stringMap, sISOMap )
620
621 QMap<QString, QString>::ConstIterator it;
622 it = sISOMap->constFind( cname );
623 if ( it != sISOMap->constEnd() ) {
624 return it.value();
625 }
626
627 QString mapfile = KGlobal::dirs()->findResource( "data",
628 QLatin1String( "kabc/countrytransl.map" ) );
629
630 QFile file( mapfile );
631 if ( file.open( QIODevice::ReadOnly ) ) {
632 QTextStream s( &file );
633 QString strbuf = s.readLine();
634 while ( !strbuf.isEmpty() ) {
635 QStringList countryInfo = strbuf.split( QLatin1Char( '\t' ), QString::KeepEmptyParts );
636 if ( countryInfo[ 0 ] == cname ) {
637 file.close();
638 sISOMap->insert( cname, countryInfo[ 1 ] );
639 return countryInfo[ 1 ];
640 }
641 strbuf = s.readLine();
642 }
643 file.close();
644 }
645
646 // fall back to system country
647 sISOMap->insert( cname, KGlobal::locale()->country() );
648 return KGlobal::locale()->country();
649}
650
651QString Address::ISOtoCountry( const QString &ISOname )
652{
653 // get country name from ISO country code (e.g. "no" -> i18n("Norway"))
654 if ( ISOname.simplified().isEmpty() ) {
655 return QString();
656 }
657
658 QString mapfile = KGlobal::dirs()->findResource( "data",
659 QLatin1String( "kabc/countrytransl.map" ) );
660
661 QFile file( mapfile );
662 if ( file.open( QIODevice::ReadOnly ) ) {
663 QTextStream s( &file );
664 QString searchStr = QLatin1Char( '\t' ) + ISOname.simplified().toLower();
665 QString strbuf = s.readLine();
666 int pos;
667 while ( !strbuf.isEmpty() ) {
668 if ( ( pos = strbuf.indexOf( searchStr ) ) != -1 ) {
669 file.close();
670 return i18n( strbuf.left( pos ).toUtf8() );
671 }
672 strbuf = s.readLine();
673 }
674 file.close();
675 }
676
677 return ISOname;
678}
679
680QDataStream &KABC::operator<<( QDataStream &s, const Address &addr )
681{
682 return s << addr.d->mId << (uint)addr.d->mType << addr.d->mPostOfficeBox
683 << addr.d->mExtended << addr.d->mStreet << addr.d->mLocality
684 << addr.d->mRegion << addr.d->mPostalCode << addr.d->mCountry
685 << addr.d->mLabel << addr.d->mEmpty;
686}
687
688QDataStream &KABC::operator>>( QDataStream &s, Address &addr )
689{
690 uint type;
691 s >> addr.d->mId >> type >> addr.d->mPostOfficeBox >> addr.d->mExtended
692 >> addr.d->mStreet >> addr.d->mLocality >> addr.d->mRegion
693 >> addr.d->mPostalCode >> addr.d->mCountry >> addr.d->mLabel
694 >> addr.d->mEmpty;
695
696 addr.d->mType = Address::Type( type );
697
698 return s;
699}
700