File indexing completed on 2025-01-19 03:39:50
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 }