File indexing completed on 2024-04-14 03:51:24

0001 /*
0002     SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "addressformatter_p.h"
0007 
0008 #include "address.h"
0009 #include "addressformat.h"
0010 #include "addressformat_p.h"
0011 #include "addressformatscript_p.h"
0012 
0013 #include <KCountry>
0014 
0015 #include <QDebug>
0016 #include <QStringList>
0017 
0018 using namespace KContacts;
0019 
0020 static constexpr auto AllFields = AddressFormatField::Country | AddressFormatField::Region | AddressFormatField::Locality
0021     | AddressFormatField::DependentLocality | AddressFormatField::SortingCode | AddressFormatField::PostalCode | AddressFormatField::StreetAddress
0022     | AddressFormatField::Organization | AddressFormatField::Name | AddressFormatField::PostOfficeBox;
0023 static constexpr auto AllDomesticFields = AllFields & ~(int)AddressFormatField::Country;
0024 static constexpr auto GeoUriFields = AddressFormatField::StreetAddress | AddressFormatField::PostalCode | AddressFormatField::Locality
0025     | AddressFormatField::DependentLocality | AddressFormatField::Region | AddressFormatField::Country;
0026 
0027 enum Separator { Newline, Comma, Native };
0028 
0029 // keep the same order as the enum!
0030 struct {
0031     Separator separator;
0032     bool honorUpper;
0033     bool forceCountry;
0034     AddressFormatFields includeFields;
0035 } static constexpr const style_map[] = {
0036     {Newline, true, true, AllDomesticFields}, // AddressFormatStyle::Postal
0037     {Newline, false, false, AllDomesticFields}, // AddressFormatStyle::MultiLineDomestic
0038     {Newline, false, true, AllFields}, // AddressFormatStyle::MultiLineInternational
0039     {Native, false, false, AllDomesticFields}, // AddressFormatStyle::SingleLineDomestic
0040     {Native, false, true, AllFields}, // AddressFormatStyle::SingleLineInternational
0041     {Comma, true, true, GeoUriFields}, // AddressFormatStyle::GeoUriQuery
0042 };
0043 
0044 static constexpr const char *separator_map[] = {"\n", ","};
0045 static constexpr const char *native_separator_map[] = {", ", "، ", "", " "};
0046 
0047 static bool isReverseOrder(const AddressFormat &fmt)
0048 {
0049     return !fmt.elements().empty() && fmt.elements()[0].field() == AddressFormatField::Country;
0050 }
0051 
0052 QString
0053 AddressFormatter::format(const Address &address, const QString &name, const QString &organization, const AddressFormat &format, AddressFormatStyle style)
0054 {
0055     const auto styleData = style_map[(int)style];
0056     const auto isFieldEmpty = [&](AddressFormatField f) -> bool {
0057         if ((styleData.includeFields & f) == 0) {
0058             return true;
0059         }
0060         switch (f) {
0061         case AddressFormatField::NoField:
0062         case AddressFormatField::DependentLocality:
0063         case AddressFormatField::SortingCode:
0064             return true;
0065         case AddressFormatField::Name:
0066             return name.isEmpty();
0067         case AddressFormatField::Organization:
0068             return organization.isEmpty();
0069         case AddressFormatField::PostOfficeBox:
0070             return address.postOfficeBox().isEmpty();
0071         case AddressFormatField::StreetAddress:
0072             return address.street().isEmpty() && (address.extended().isEmpty() || style == AddressFormatStyle::GeoUriQuery);
0073         case AddressFormatField::PostalCode:
0074             return address.postalCode().isEmpty();
0075         case AddressFormatField::Locality:
0076             return address.locality().isEmpty();
0077         case AddressFormatField::Region:
0078             return address.region().isEmpty();
0079         case AddressFormatField::Country:
0080             return address.country().isEmpty();
0081         }
0082         return true;
0083     };
0084     const auto countryName = [&]() -> QString {
0085         if (address.country().isEmpty()) {
0086             return {};
0087         }
0088         // we use the already ISO 3166-1 resolved country from format here to
0089         // avoid a potentially expensive second name-based lookup
0090         return style == AddressFormatStyle::GeoUriQuery ? format.country() : KCountry::fromAlpha2(format.country()).name();
0091     };
0092 
0093     QStringList lines;
0094     QString line, secondaryLine;
0095 
0096     for (auto it = format.elements().begin(); it != format.elements().end(); ++it) {
0097         // add separators if:
0098         // - the preceding line is not empty
0099         // - we use newline separators and the preceding element is another separator
0100         const auto precedingSeparator = (it != format.elements().begin() && (*std::prev(it)).isSeparator());
0101         if ((*it).isSeparator() && (!line.isEmpty() || (precedingSeparator && styleData.separator == Newline))) {
0102             lines.push_back(line);
0103             line.clear();
0104             if (!secondaryLine.isEmpty()) {
0105                 lines.push_back(secondaryLine);
0106                 secondaryLine.clear();
0107             }
0108             continue;
0109         }
0110 
0111         // literals are only added if they not follow an empty field and are not preceding an empty field
0112         // to support incomplete addresses we deviate from the libaddressinput algorithm here and also add
0113         // the separator if any preceding field in the same line is non-empty, not just the immediate one.
0114         // this is to produce useful output e.g. for "%C %S %Z" if %S is empty.
0115         bool precedingFieldHasContent = (it == format.elements().begin() || (*std::prev(it)).isSeparator());
0116         for (auto it2 = it; !(*it2).isSeparator(); --it2) {
0117             if ((*it2).isField() && !isFieldEmpty((*it2).field())) {
0118                 precedingFieldHasContent = true;
0119                 break;
0120             }
0121             if (it2 == format.elements().begin()) {
0122                 break;
0123             }
0124         }
0125         const auto followingFieldEmpty = (std::next(it) != format.elements().end() && (*std::next(it)).isField() && isFieldEmpty((*std::next(it)).field()));
0126         if ((*it).isLiteral() && precedingFieldHasContent && !followingFieldEmpty) {
0127             line += (*it).literal();
0128             continue;
0129         }
0130 
0131         if ((*it).isField() && (styleData.includeFields & (*it).field())) {
0132             QString v;
0133             switch ((*it).field()) {
0134             case AddressFormatField::NoField:
0135             case AddressFormatField::DependentLocality:
0136             case AddressFormatField::SortingCode:
0137                 break;
0138             case AddressFormatField::Name:
0139                 v = name;
0140                 break;
0141             case AddressFormatField::Organization:
0142                 v = organization;
0143                 break;
0144             case AddressFormatField::PostOfficeBox:
0145                 v = address.postOfficeBox();
0146                 break;
0147             case AddressFormatField::StreetAddress:
0148                 if (!address.street().isEmpty() && !address.extended().isEmpty() && style != AddressFormatStyle::GeoUriQuery) {
0149                     if (isReverseOrder(format)) {
0150                         secondaryLine = address.extended();
0151                     } else {
0152                         lines.push_back(address.extended());
0153                     }
0154                 }
0155                 v = address.street().isEmpty() ? address.extended() : address.street();
0156                 break;
0157             case AddressFormatField::PostalCode:
0158                 v = address.postalCode();
0159                 break;
0160             case AddressFormatField::Locality:
0161                 v = address.locality();
0162                 break;
0163             case AddressFormatField::Region:
0164                 v = address.region();
0165                 break;
0166             case AddressFormatField::Country:
0167                 v = countryName();
0168                 break;
0169             }
0170             if (styleData.honorUpper && format.upperCaseFields() & (*it).field()) {
0171                 v = v.toUpper();
0172             }
0173             line += v;
0174         }
0175     }
0176     if (!line.isEmpty()) {
0177         lines.push_back(line);
0178     }
0179     if (!secondaryLine.isEmpty()) {
0180         lines.push_back(secondaryLine);
0181     }
0182 
0183     // append country for formats that need it (international style + not yet present in format.elements())
0184     if (styleData.forceCountry && (format.usedFields() & AddressFormatField::Country & styleData.includeFields) == 0 && !address.country().isEmpty()) {
0185         auto c = countryName();
0186         if (style == AddressFormatStyle::Postal) {
0187             // the format of the country for postal addresses depends on the sending country, not the destination
0188             const auto sourceCountry = KCountry::fromQLocale(QLocale().territory());
0189             const auto sourceFmt = AddressFormatRepository::formatForCountry(sourceCountry.alpha2(), AddressFormatScriptPreference::Local);
0190             const auto shouldPrepend = isReverseOrder(sourceFmt);
0191             if (!lines.isEmpty()) {
0192                 shouldPrepend ? lines.push_front({}) : lines.push_back({});
0193             }
0194             if (styleData.honorUpper && (sourceFmt.upperCaseFields() & AddressFormatField::Country)) {
0195                 c = c.toUpper();
0196             }
0197             shouldPrepend ? lines.push_front(c) : lines.push_back(c);
0198         } else {
0199             if (styleData.honorUpper && (format.upperCaseFields() & AddressFormatField::Country)) {
0200                 c = c.toUpper();
0201             }
0202             lines.push_back(c);
0203         }
0204     }
0205 
0206     if (styleData.separator == Native) {
0207         const auto script = AddressFormatScript::detect(address);
0208         return lines.join(QString::fromUtf8(native_separator_map[script]));
0209     }
0210     return lines.join(QLatin1String(separator_map[styleData.separator]));
0211 }