KItinerary

addressparser.cpp
1/*
2 SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "addressparser_p.h"
7
8#include <KContacts/AddressFormat>
9
10#include <QDebug>
11#include <QRegularExpression>
12
13using namespace KItinerary;
14
15AddressParser::AddressParser() = default;
16AddressParser::~AddressParser() = default;
17
18void AddressParser::setFallbackCountry(const QString &countryCode)
19{
20 m_fallbackCountry = countryCode;
21}
22
23void AddressParser::parse(PostalAddress addr)
24{
25 m_address = addr;
26 if ((m_address.postalCode().isEmpty() && !m_address.addressLocality().isEmpty()) ||
27 (!m_address.postalCode().isEmpty() && m_address.addressLocality().contains(m_address.postalCode())))
28 {
29 splitPostalCode();
30 }
31
32 // crude fallback if the above doesn't work (yet), e.g. in the UK
33 if (!m_address.postalCode().isEmpty() && m_address.addressLocality().contains(m_address.postalCode())) {
34 m_address.setAddressLocality(m_address.addressLocality().remove(m_address.postalCode()).trimmed());
35 }
36}
37
38PostalAddress AddressParser::result() const
39{
40 return m_address;
41}
42
43KContacts::AddressFormat AddressParser::addressFormat() const
44{
45 // TODO detect script
46 return KContacts::AddressFormatRepository::formatForCountry(m_address.addressCountry().isEmpty() ? m_fallbackCountry : m_address.addressCountry(), KContacts::AddressFormatScriptPreference::Local);
47}
48
49static QString captureName(KContacts::AddressFormatField field)
50{
51 switch (field) {
52 case KContacts::AddressFormatField::PostalCode:
53 return QStringLiteral("postalCode");
54 case KContacts::AddressFormatField::Locality:
55 return QStringLiteral("locality");
56 default:
57 return {};
58 }
59}
60
61static QString captureExpression(KContacts::AddressFormatField field)
62{
63 return QLatin1StringView("?<") + captureName(field) + QLatin1Char('>');
64}
65
66void AddressParser::splitPostalCode()
67{
68 const auto format = addressFormat();
69 if (format.elements().empty() || format.postalCodeRegularExpression().isEmpty()) {
70 return;
71 }
72
73 // find the format line containing the postal code and locality
74 using namespace KContacts;
75 auto startIt = format.elements().begin();
76 auto endIt = startIt;
77 enum {
78 None = 0,
79 HasLocality = 1,
80 HasPostalCode = 2,
81 HasBoth = 3,
82 };
83 int inRelevantLine = None;
84 for (auto it = format.elements().begin(); it != format.elements().end(); ++it) {
85 if ((*it).isSeparator() && inRelevantLine != HasBoth) {
86 startIt = endIt = it;
87 inRelevantLine = None;
88 }
89 if ((*it).isSeparator() && inRelevantLine == HasBoth) {
90 endIt = it;
91 inRelevantLine = None;
92 break;
93 }
94 if ((*it).isField() && (*it).field() == AddressFormatField::Locality) {
95 inRelevantLine |= HasLocality;
96 }
97 if ((*it).isField() && (*it).field() == AddressFormatField::PostalCode) {
98 inRelevantLine |= HasPostalCode;
99 }
100 }
101 if (inRelevantLine == HasBoth) {
102 endIt = format.elements().end();
103 }
104 std::vector<AddressFormatElement> line(startIt, endIt);
105 // TODO also handle the case the region is part of the same line
106 if (line.empty() || std::count_if(line.begin(), line.end(), std::mem_fn(&AddressFormatElement::isField)) > 2) {
107 return;
108 }
109
110 // build regex for that format line
111 QString regex;
112 regex.push_back(QLatin1Char('^'));
113 for (auto it = line.begin(); it != line.end(); ++it) {
114 if ((*it).isField()) {
115 regex += QLatin1Char('(') + captureExpression((*it).field()) +
116 ((*it).field() == AddressFormatField::PostalCode
117 ? format.postalCodeRegularExpression()
118 : QLatin1StringView("\\S.*")) +
119 QLatin1Char(')');
120 }
121 if ((*it).isLiteral()) {
122 regex += (*it).literal();
123 }
124 }
125
126 QRegularExpression re(regex);
127 if (!re.isValid()) {
128 qWarning() << "generated invalid address parsing pattern:" << regex;
129 return;
130 }
131
132 // match against the input
133 const auto match = re.match(m_address.addressLocality());
134 if (!match.hasMatch()) {
135 return;
136 }
137
138 const auto postalCode = match.captured(captureName(AddressFormatField::PostalCode));
139 const auto locality = match.captured(captureName(AddressFormatField::Locality));
140 if (!locality.isEmpty() && !postalCode.isEmpty()) {
141 m_address.setPostalCode(postalCode);
142 m_address.setAddressLocality(locality);
143 }
144}
static Q_INVOKABLE KContacts::AddressFormat formatForCountry(const QString &countryCode, KContacts::AddressFormatScriptPreference scriptPref, KContacts::AddressFormatPreference formatPref=AddressFormatPreference::Generic)
Postal address.
Definition place.h:46
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
QStringView countryCode(QStringView coachNumber)
void push_back(QChar ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:49 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.