KContacts

addressformatter.cpp
1/*
2 SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "addressformatter_p.h"
7
8#include "address.h"
9#include "addressformat.h"
10#include "addressformat_p.h"
11#include "addressformatscript_p.h"
12
13#include <KCountry>
14
15#include <QDebug>
16#include <QStringList>
17
18using namespace KContacts;
19
20static constexpr auto AllFields = AddressFormatField::Country | AddressFormatField::Region | AddressFormatField::Locality
21 | AddressFormatField::DependentLocality | AddressFormatField::SortingCode | AddressFormatField::PostalCode | AddressFormatField::StreetAddress
22 | AddressFormatField::Organization | AddressFormatField::Name | AddressFormatField::PostOfficeBox;
23static constexpr auto AllDomesticFields = AllFields & ~(int)AddressFormatField::Country;
24static constexpr auto GeoUriFields = AddressFormatField::StreetAddress | AddressFormatField::PostalCode | AddressFormatField::Locality
25 | AddressFormatField::DependentLocality | AddressFormatField::Region | AddressFormatField::Country;
26
27enum Separator { Newline, Comma, Native };
28
29// keep the same order as the enum!
30struct {
31 Separator separator;
32 bool honorUpper;
33 bool forceCountry;
34 AddressFormatFields includeFields;
35} static constexpr const style_map[] = {
36 {Newline, true, true, AllDomesticFields}, // AddressFormatStyle::Postal
37 {Newline, false, false, AllDomesticFields}, // AddressFormatStyle::MultiLineDomestic
38 {Newline, false, true, AllFields}, // AddressFormatStyle::MultiLineInternational
39 {Native, false, false, AllDomesticFields}, // AddressFormatStyle::SingleLineDomestic
40 {Native, false, true, AllFields}, // AddressFormatStyle::SingleLineInternational
41 {Comma, true, true, GeoUriFields}, // AddressFormatStyle::GeoUriQuery
42};
43
44static constexpr const char *separator_map[] = {"\n", ","};
45static constexpr const char *native_separator_map[] = {", ", "، ", "", " "};
46
47static bool isReverseOrder(const AddressFormat &fmt)
48{
49 return !fmt.elements().empty() && fmt.elements()[0].field() == AddressFormatField::Country;
50}
51
53AddressFormatter::format(const Address &address, const QString &name, const QString &organization, const AddressFormat &format, AddressFormatStyle style)
54{
55 const auto styleData = style_map[(int)style];
56 const auto isFieldEmpty = [&](AddressFormatField f) -> bool {
57 if ((styleData.includeFields & f) == 0) {
58 return true;
59 }
60 switch (f) {
61 case AddressFormatField::NoField:
62 case AddressFormatField::DependentLocality:
63 case AddressFormatField::SortingCode:
64 return true;
65 case AddressFormatField::Name:
66 return name.isEmpty();
67 case AddressFormatField::Organization:
68 return organization.isEmpty();
69 case AddressFormatField::PostOfficeBox:
70 return address.postOfficeBox().isEmpty();
71 case AddressFormatField::StreetAddress:
72 return address.street().isEmpty() && (address.extended().isEmpty() || style == AddressFormatStyle::GeoUriQuery);
73 case AddressFormatField::PostalCode:
74 return address.postalCode().isEmpty();
75 case AddressFormatField::Locality:
76 return address.locality().isEmpty();
77 case AddressFormatField::Region:
78 return address.region().isEmpty();
79 case AddressFormatField::Country:
80 return address.country().isEmpty();
81 }
82 return true;
83 };
84 const auto countryName = [&]() -> QString {
85 if (address.country().isEmpty()) {
86 return {};
87 }
88 // we use the already ISO 3166-1 resolved country from format here to
89 // avoid a potentially expensive second name-based lookup
90 return style == AddressFormatStyle::GeoUriQuery ? format.country() : KCountry::fromAlpha2(format.country()).name();
91 };
92
93 QStringList lines;
94 QString line, secondaryLine;
95
96 for (auto it = format.elements().begin(); it != format.elements().end(); ++it) {
97 // add separators if:
98 // - the preceding line is not empty
99 // - we use newline separators and the preceding element is another separator
100 const auto precedingSeparator = (it != format.elements().begin() && (*std::prev(it)).isSeparator());
101 if ((*it).isSeparator() && (!line.isEmpty() || (precedingSeparator && styleData.separator == Newline))) {
102 lines.push_back(line);
103 line.clear();
104 if (!secondaryLine.isEmpty()) {
105 lines.push_back(secondaryLine);
106 secondaryLine.clear();
107 }
108 continue;
109 }
110
111 // literals are only added if they not follow an empty field and are not preceding an empty field
112 // to support incomplete addresses we deviate from the libaddressinput algorithm here and also add
113 // the separator if any preceding field in the same line is non-empty, not just the immediate one.
114 // this is to produce useful output e.g. for "%C %S %Z" if %S is empty.
115 bool precedingFieldHasContent = (it == format.elements().begin() || (*std::prev(it)).isSeparator());
116 for (auto it2 = it; !(*it2).isSeparator(); --it2) {
117 if ((*it2).isField() && !isFieldEmpty((*it2).field())) {
118 precedingFieldHasContent = true;
119 break;
120 }
121 if (it2 == format.elements().begin()) {
122 break;
123 }
124 }
125 const auto followingFieldEmpty = (std::next(it) != format.elements().end() && (*std::next(it)).isField() && isFieldEmpty((*std::next(it)).field()));
126 if ((*it).isLiteral() && precedingFieldHasContent && !followingFieldEmpty) {
127 line += (*it).literal();
128 continue;
129 }
130
131 if ((*it).isField() && (styleData.includeFields & (*it).field())) {
132 QString v;
133 switch ((*it).field()) {
134 case AddressFormatField::NoField:
135 case AddressFormatField::DependentLocality:
136 case AddressFormatField::SortingCode:
137 break;
138 case AddressFormatField::Name:
139 v = name;
140 break;
141 case AddressFormatField::Organization:
142 v = organization;
143 break;
144 case AddressFormatField::PostOfficeBox:
145 v = address.postOfficeBox();
146 break;
147 case AddressFormatField::StreetAddress:
148 if (!address.street().isEmpty() && !address.extended().isEmpty() && style != AddressFormatStyle::GeoUriQuery) {
149 if (isReverseOrder(format)) {
150 secondaryLine = address.extended();
151 } else {
152 lines.push_back(address.extended());
153 }
154 }
155 v = address.street().isEmpty() ? address.extended() : address.street();
156 break;
157 case AddressFormatField::PostalCode:
158 v = address.postalCode();
159 break;
160 case AddressFormatField::Locality:
161 v = address.locality();
162 break;
163 case AddressFormatField::Region:
164 v = address.region();
165 break;
166 case AddressFormatField::Country:
167 v = countryName();
168 break;
169 }
170 if (styleData.honorUpper && format.upperCaseFields() & (*it).field()) {
171 v = v.toUpper();
172 }
173 line += v;
174 }
175 }
176 if (!line.isEmpty()) {
177 lines.push_back(line);
178 }
179 if (!secondaryLine.isEmpty()) {
180 lines.push_back(secondaryLine);
181 }
182
183 // append country for formats that need it (international style + not yet present in format.elements())
184 if (styleData.forceCountry && (format.usedFields() & AddressFormatField::Country & styleData.includeFields) == 0 && !address.country().isEmpty()) {
185 auto c = countryName();
186 if (style == AddressFormatStyle::Postal) {
187 // the format of the country for postal addresses depends on the sending country, not the destination
188 const auto sourceCountry = KCountry::fromQLocale(QLocale().territory());
189 const auto sourceFmt = AddressFormatRepository::formatForCountry(sourceCountry.alpha2(), AddressFormatScriptPreference::Local);
190 const auto shouldPrepend = isReverseOrder(sourceFmt);
191 if (!lines.isEmpty()) {
192 shouldPrepend ? lines.push_front({}) : lines.push_back({});
193 }
194 if (styleData.honorUpper && (sourceFmt.upperCaseFields() & AddressFormatField::Country)) {
195 c = c.toUpper();
196 }
197 shouldPrepend ? lines.push_front(c) : lines.push_back(c);
198 } else {
199 if (styleData.honorUpper && (format.upperCaseFields() & AddressFormatField::Country)) {
200 c = c.toUpper();
201 }
202 lines.push_back(c);
203 }
204 }
205
206 if (styleData.separator == Native) {
207 const auto script = AddressFormatScript::detect(address);
208 return lines.join(QString::fromUtf8(native_separator_map[script]));
209 }
210 return lines.join(QLatin1String(separator_map[styleData.separator]));
211}
static Q_INVOKABLE KContacts::AddressFormat formatForCountry(const QString &countryCode, KContacts::AddressFormatScriptPreference scriptPref, KContacts::AddressFormatPreference formatPref=AddressFormatPreference::Generic)
Look up format data for a country.
Information on how addresses are formatted in a specific country/language.
Postal address information.
Definition address.h:31
static KCountry fromAlpha2(const char *alpha2Code)
QString name() const
static KCountry fromQLocale(QLocale::Country country)
PostalAddress address(const QVariant &location)
QString name(StandardShortcut id)
AddressFormatStyle
Address formatting styles.
Definition namespace.h:25
AddressFormatField
Address field types.
Definition namespace.h:44
bool isEmpty() const const
void push_back(parameter_type value)
void push_front(parameter_type value)
void clear()
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString toUpper() const const
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:08 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.